left-icon

Gradle Succinctly®
by José Roberto Olivas Mendoza

Previous
Chapter

of
A
A
A

CHAPTER 5

Digging into Gradle Tasks

Digging into Gradle Tasks


As explained in Chapter 4, a task is the fundamental unit of build activity, and it’s identified within the build script with a name. Going a little further, a task is a named collection of build instructions that Gradle executes during a build process.

More about task declarations

All the samples discussed in earlier chapters of this book showed how to create a task and define its behavior at the same time. However, there’s a simpler way to declare a task—all that’s needed is a name. This way is displayed in the following sample.

Code Listing  22

task onlyname

If this task is executed with gradle -q onlyname, it will not produce any result. This is because this task hasn’t had an action assigned yet.

Earlier in this book, a task was bound to an action using the left-shift (<<) operator and a set of statements within curly braces.

Code Listing  23

task nameandaction << {

   println 'An action was assigned to this task'

}

But Gradle also allows you to associate action to a task by referring to the task object created with the task declaration. This is displayed in the following sample.

Code Listing  24

task nameandaction

 

nameandaction << {

   println 'An action was assigned to this task'

}

nameandaction << {

   println 'A second action was assigned to this task'

}

The output for this example should look like this.

Code Listing  25

An action was assigned to this task

A second action was assigned to this task

Even though the previous example reflects a trivial build behavior, it exposes a powerful insight: tasks aren’t one-off declarations of build activity, but first-class objects in Gradle programming.

Since build actions can be assigned to tasks over the course of the build file, there’s more you can do with tasks. This chapter will go a little further in exploring the capabilities of tasks.

Note: In Groovy, operators like << (left-shift operator from Java) can be overloaded to have different meanings, depending on the types of the objects they operate on. Gradle has overloaded the << operator to append a code block to the list of actions a task performs. The equivalent of this is the doLast() method, which will be covered later in this book.

Configuration blocks

Considering the example discussed in the previous section, take a look at the following code:

Code Listing  26

task extractDatabase

 

extractDatabase << {

   println 'Connect to database'

}

extractDatabase << {

   println 'Extracting database data'

}

extractDatabase << {

   println 'Closing database connection'

}

extractDatabase {

   println 'Setting up database connection'

}

It is expected that all code blocks were build actions snippets. So, you might expect that the message for the fourth block will be printed at the end. But instead, the following output is displayed.

Code Listing  27

Setting up database connection

Connect to database

Extracting database data

Closing database connection

Why is that?

In Gradle, when a code block with no left-shift operator is added to a task name, this code block is treated as a configuration block. This means that the code block will be run during Gradle’s configuration phase, according to the project lifecycle detailed in Chapter 4. This phase runs before the execution phase, when task actions are executed.

A configuration block can be additive just like action blocks, so the following code will work exactly as the example shown at the beginning of this section.

Code Listing  28

task extractDatabase

 

extractDatabase << {

   println 'Connect to database'

}

extractDatabase << {

   println 'Extracting database data'

}

extractDatabase << {

   println 'Closing database connection'

}

extractDatabase {

   print 'Setting up '

}

extractDatabase {

   println 'database connection'

}

A configuration block is useful to set up variables and data structures that will be needed by the task action when it runs later on in the build. This gives the user an opportunity to turn a build’s tasks into a rich object model populated with information about the build.

All build configuration code runs every time Gradle executes a build script, regardless of whether any given task runs during execution.

Note: As discussed in Chapter 4, every time Gradle executes a build, it runs through three lifecycle phases: initialization, configuration, and execution. Build tasks are executed in the execution phase, following the order required by their dependency relationships. Those task objects are assembled into an internal object model during the configuration phase. This internal object model is usually called the DAG (Directed Acyclic Graph). During initialization phase, Gradle decides which projects are going to participate in the build.

Task object model basics

Every time Gradle executes a build, it creates an internal object model beforehand, so every task declared is a task object contained within the overall project. Like any object, a task object has properties and methods.

The default type for each new task is DefaultTask. Every Gradle task descends from this object type. The DefaultTask contains the functionality required for them to interface with the Gradle project model.

An overview of task’s properties and methods will be discussed in the following sections.

Methods of DefaultTask

dependsOn(task)

This method adds a task as a dependency of the calling task. This depended-on task will always run before the task that depends on it. The following sample shows several ways to use this technique.

Code Listing  29

task setUpConnection {

  println 'Setting up database connection'

}

//Declares dependency using quotes (usually optional)

task connectToDatabase {

  dependsOn 'setUpConnection'

}

//Declares dependency using the task name with no quotes

task extractDatabase {

 dependsOn connectToDatabase

}

//Declares dependency using the left-shift operator

task closeConnection {

  dependsOn << extractDatabase

}

Another way to write the previous code with the same results is:

Code Listing  30

task setUpConnection {

  println 'Setting up database connection'

}

//An explicit call on the task object

task connectToDatabase

connectToDatabase.dependsOn setUpConnection

//Declares dependency using the task name with no quotes

task extractDatabase {

 dependsOn connectToDatabase

}

//A shortcut for declaring dependencies (discussed in Chapter 4)

task closeConnection(dependsOn: extractDatabase)

 

Multiple dependencies can be used. In this way, a task can depend on more than one task. The following sample shows how to achieve this.

Code Listing  31

task setUpConnection {

  println 'Setting up database connection'

}

task connectToDatabase

connectToDatabase

{

    println 'Connecting to database'

}

//Declares multi-dependency by listing tasks in a comma separated list

task extractDatabase {

 dependsOn setUpConnection, connectToDatabase

 println 'Extracting database'

}

//A shortcut for declaring dependencies (discussed in Chapter 4)

task closeConnection(dependsOn: extractDatabase) {

    println 'Database connection closed'

}

All depended-on tasks are executed following the order in which they appear in the list, starting from the left. The following figure shows the output for the previous code.

Displayed output when closeConnection task is executed.

  1. Displayed output when closeConnection task is executed.

doFirst(closure)

This method adds a block of executable code to the beginning of a task’s action.

Notice that the term closure appears within the parentheses. This term is inherited from Groovy, and it refers to a block of code between two curly braces. A closure can function like an object, and it can be passed as a parameter to a method or assigned to a variable, and then executed later.

The doFirst method can be invoked directly on the task object, passing a closure to the method. This closure contains the code to run before the task action.

Code Listing  32

task extractDatabase << {

    //This is the task action

    println 'Extracting database metadata.'

}

extractDatabase.doFirst {

    println 'Connecting to the database.'

}

The output for the previous example should look like this.

The doFirst execution sample output

  1. The doFirst execution sample output

The user can invoke doFirst from within the task’s configuration block. As discussed earlier in this chapter, all code located in those configuration blocks runs before any task action occurs, during the configuration phase of the build.

Code Listing  33

task extractDatabase << {

    //This is the task action

    println 'Extracting database metadata.'

}

extractDatabase{

    doFirst {

    println 'Connecting to the database.'

    }

}

In this example, the doFirst method is invoked within the extractDatabase configuration block. So when Gradle executes the build script, the code associated to the doFirst method is executed in the configuration phase, prior to any task action.

Repeated calls to the doFirst method can be made. These calls are additive, meaning that each previous call is retained, and the new closure is appended to the start of the list. As a result, all calls to the doFirst method will be executed in reverse order (starting from the last call and ending with the first one).

Code Listing  34

task extractDatabase << {

    //This is the task action

    println 'Extracting database metadata.'

}

extractDatabase{

    doFirst {

    println 'Connecting to the database.'

    }

    doFirst {

        println 'Setting up database connection.'

    }

}

The output for the previous code is displayed in the following figure.

Sample output for doFirst repeated calls

  1. Sample output for doFirst repeated calls

doLast(closure)

This method is similar to the doFirst method, except that the closure is appended to the end of an action. This is useful when it’s needed to execute a code block after all of a task’s activities.

Code Listing  35

task extractDatabase << {

    //This is the task action

    println 'Extracting database metadata.'

}

extractDatabase.doLast {

   println 'Closing database connection.'

}

extractDatabase{

    doFirst {

    println 'Connecting to the database.'

    }

    doFirst {

        println 'Setting up database connection.'

    }

}

The output for the doLast example

  1. The output for the doLast example

The output displayed in Figure 19 shows that the code assigned to the doLast method of the extractDatabase task is executed after all of the task’s activities. In this case, it is supposed that a database connection must be closed after extracting all data from the database itself.

The doLast method also is additive. In this way, all additive calls append their closure to the end of the execution’s list, so the calls will be executed starting from the first one, and ending with the last closure added.

Code Listing  36

task extractDatabase << {

    //This is the task action

    println 'Extracting database metadata.'

}

extractDatabase.doLast {

    println 'Zipping extracted data to backupdata.zip'

}

extractDatabase.doLast {

   println 'Closing database connection.'

}

extractDatabase{

    doFirst {

    println 'Connecting to the database.'

    }

    doFirst {

        println 'Setting up database connection.'

    }

}

As explained in the “Configuration blocks” section of this chapter, another way to express a call to the doLast method is by using the left-shift (<<) operator.

Code Listing  37

task extractDatabase << {

    //This is the task action

    println 'Extracting database metadata.'

}

extractDatabase << {

    println 'Zipping extracted data to backupdata.zip'

}

extractDatabase << {

   println 'Closing database connection.'

}

extractDatabase{

    doFirst {

    println 'Connecting to the database.'

    }

    doFirst {

        println 'Setting up database connection.'

    }

}

The output for both previous examples should look like this.

Repeated calls to the doLast method

  1. Repeated calls to the doLast method

onlyIf (closure)

This method allows you to express a predicate that determines whether a task should be executed. The predicate corresponds to a closure with a returning value, which is the value that will be assigned to the predicate. Gradle uses a switch logic (on or off) to execute the task, or to ignore it. So, the value assigned to the predicate should be a Boolean value.

Code Listing  38

/* This is the task that receives the predicate. */

task extractMetadata << {

        println 'Extacting metadata.'

}

/* The predicate is assigned to extractMetadata. */

/* The task will be executed if the property extract.metadata is equal to true. */

extractMetadata.onlyIf {

    System.properties['extract.metadata'] == 'true'

}

task extractData << {

    println 'Extracting database data.'

}

/* This is the main task. */

task extractDatabase << { 

    //This is the task action

}

/* Code that will be executed after extractDatabase action ends. */

extractDatabase << {

    println 'Zipping extracted data to backupdata.zip'

}

extractDatabase << {

   println 'Closing database connection.'

}

/* extractDatabase will depend on extractData and extractMetadata. */

/* extractData is executed first, then extractMetadata will be executed depending on the predicate. */

extractDatabase {

    dependsOn extractData, extractMetadata

}

/* Code that will be executed before extractData task action begins is set up at configuration phase. */

extractData {

    doFirst {

    println 'Connecting to the database.'

    }

    doFirst {

        println 'Setting up database connection.'

    }

}

The previous example can be called in two ways. For the purposes of the  onlyIf property explanation, the -q (quiet mode) option will be omitted in both calls. The first one looks like this:

Code Listing  39T

gradle extractDatabase

The following figure shows the output displayed.

A call to extractDatabase without a value for extract.metadata property

  1. A call to extractDatabase without a value for extract.metadata property

Notice that the extractMetadata task was skipped for this call. The reason for this relies on the absence of a value for the extract.metadata property defined in the predicate closure. Gradle assumes that the property has a value of false, and omits the execution of the extractMetadata task.

To assign a value to the extract.metadata property, the -D option of Gradle’s command line should be used. The call to extractDatabase task should look like the following sample.

Code Listing  40

gradle -Dextract.metadata=true extractDatabase

The output for this call is shown in the following figure.

The result of calling extractDatabase with a given value of true for extract.metadata

  1. The result of calling extractDatabase with a given value of true for extract.metadata

The predicate for the onlyIf method can use any kind of code in order to return a true or a false value. So, this is not limited to System properties to make the test—you can read files, call a web service, check credentials, or anything else.

Properties of DefaultTask

didWork

This Boolean property indicates whether a task was completed successfully. You can set the didWork property in its own task actions, in order to reflect the results of build code.

In the following example, an implementation of the Java Compiler will return a didWork value of true if at least one file was successfully compiled.

Code Listing  41

/* Applying the Java plugin for this Gradle build. */

apply plugin: 'java'

/* Making emailMe task depend on Java compilation.

 The task compileJava will return true for didWork

 property if at least one file was successfully

 compiled. */

task emailMe(dependsOn: compileJava) << {

    if (tasks.compileJava.didWork) {

        println 'Send Email announcing success'

    }

   

}

The output for this build should look like this.

Using didWork property.

  1. Using didWork property.

enabled

This Boolean property indicates whether the task will be executed. You can set any task’s enabled property value to false, which will prevent Gradle from executing the task. If there are any dependencies, they’re still going to execute as if the task were enabled.

The example for the onlyIf method, explained in the “Methods” section of this chapter, will be taken to demonstrate the enabled property. This example will be modified with a few changes.

Code Listing  42

/* This is the task that will be enabled

   on demand. */

task extractMetadata << {

        println 'Extacting metadata.'

}

task extractData << {

    println 'Extracting database data.'

}

/* Code that will be executed before extractData task action begins, is set up at configuration phase. */

extractData {

    doFirst {

    println 'Connecting to the database.'

    }

    doFirst {

        println 'Setting up database connection.'

    }

}

/* This is the main task. */

task extractDatabase (dependsOn: [extractMetadata, extractData]) << {

    //This is the task action

}

/* A closure is assigned to the extractDatabase task

   at configuration phase, so the code will be executed

   before Gradle enters to the execution phase.

  

   The tasks collection is used to access the extractMetadata

   task object, in order to set the value for enabled property.

  

   The value for enabled property depends on the extract.metadata

   System property. If this propety is set to true, the extractMetadata

   task will be executed.

*/

extractDatabase {

    tasks['extractMetadata'].enabled = (System.properties['extract.metadata'] == 'true')

}

/* Code that will be executed after extractDatabase action ends. */

extractDatabase << {

    println 'Zipping extracted data to backupdata.zip'

}

extractDatabase << {

   println 'Closing database connection.'

}

Like the onlyIf example, this build script can be called in two ways—one of them assigning the value of true to the extractMetadata property. The output for both ways is displayed in the following figure.

Results for enabled property example

  1. Results for enabled property example

path

This string property contains the fully qualified path of the task object. A task’s path is, by default, simply the name of the task with a leading colon. The leading colon indicates that the task is located in the top-level build script file. Since Gradle supports dependent subprojects or nested builds, if the task existed in a nested build, then the path would be :nestedBuild:taskName.

The enabled property example was modified to display all tasks’ paths along the build execution.

Code Listing  43

/* This is the task that will be enabled

   on demand. */

task extractMetadata << {

        println "This Task's path is ${path}"  //Displaying task's path

        println 'Extacting metadata.'

}

task extractData << {

    println "This Task's path is ${path}" //Displaying task's path

    println 'Extracting database data.'

}

/* Code that will be executed before extractData task action begins, is set up at configuration phase. */

extractData {

    doFirst {

    println 'Connecting to the database.'

    }

    doFirst {

        println 'Setting up database connection.'

    }

}

/* This is the main task. */

task extractDatabase (dependsOn: [extractMetadata, extractData]) << {

    println "This Task's path is ${path}" //Displaying task's path

    //This is the task action

}

/* A closure is assigned to the extractDatabase task

   at configuration phase, so the code will be executed

   before Gradle enters to the execution phase.

  

   The tasks collection is used to access the extractMetadata

   task object, in order to set the value for enabled property.

  

   The value for enabled property depends on the extract.metadata

   System property. If this propety is set to true, the extractMetadata

   task will be executed.

*/

extractDatabase {

    tasks['extractMetadata'].enabled = (System.properties['extract.metadata'] == 'true')

}

/* Code that will be executed after extractDatabase action ends. */

extractDatabase << {

    println 'Zipping extracted data to backupdata.zip'

}

extractDatabase << {

   println 'Closing database connection.'

}

The output should look like this:

Displaying the task’s path

  1. Displaying the task’s path

description

This string property is a small piece of human-readable metadata, used to document the purpose of a task. The following sample shows some ways to set a description.

Code Listing  44

task extractMetadata(description: 'Extracts database metadata') << {

        println "This Task's path is ${path}"  //Displaying task's path

        println 'Extacting metadata.'

}

task extractData << {

    println "This Task's path is ${path}" //Displaying task's path

    println 'Extracting database data.'

}

/* Code that will be executed before extractData task action begins is set up at configuration phase. */

extractData {

    description = 'Extracts data from the database'

    doFirst {

    println 'Connecting to the database.'

    }

    doFirst {

        println 'Setting up database connection.'

    }

}

/* This is the main task. */

task extractDatabase (dependsOn: [extractMetadata, extractData]) << {

    println "This Task's path is ${path}" //Displaying task's path

    //This is the task action

}

extractDatabase.description = 'Entry point for extractDatabase build'

/* A closure is assigned to the extractDatabase task

   at configuration phase, so the code will be executed

   before Gradle enters the execution phase.

  

   The tasks collection is used to access the extractMetadata

   task object, in order to set the value for enabled property.

  

   The value for enabled property depends on the extract.metadata

   System property. If this property is set to true, the extractMetadata

   task will be executed.

*/

extractDatabase {

    tasks['extractMetadata'].enabled = (System.properties['extract.metadata'] == 'true')

}

/* Code that will be executed after extractDatabase action ends. */

extractDatabase << {

    println 'Zipping extracted data to backupdata.zip'

}

extractDatabase << {

   println 'Closing database connection.'

}

Since the description is used for documentation purposes only, the output is the same as was shown in the section on the path property, previously discussed in this chapter.

About task types

According to the “Task object model basics” section of this chapter, every task has a type. The first type for a task is the DefaultTask, but there are also task types for copying, archiving, executing programs, and many more.

A complete task reference is beyond the scope of this book, but a few important types will be discussed in this section.

Copy

A copy task copies files from one place into another. Its most basic form copies files from one directory to another. There are optional restrictions for performing this action, like file patterns to be included or to be excluded.

Code Listing  45

task copyFiles(type: Copy){

    from 'source'

    into 'target'

    include '**/*.txt'

}

task backupFiles(dependsOn: copyFiles) << {

    println 'Backup completed'

}

This example creates a copy of all text files located in a directory named source, and places that copy in a directory named target. If the destination directory (target, in this case) doesn’t exist, the copy task will create that directory.

This example should be executed with the following command.

Code Listing  46

gradle backupFiles

The from, into, and include methods are inherited from Copy.

Zip

A zip task creates a compressed file in .zip format. The from method can be used to specify the folder where the files to be compressed are saved.

Code Listing  47

task customZip(type: Zip){

    from 'source'

    baseName = 'backupSet'

}

/* Using Groovy string interpolation to display zip file name. */

println "The ${customZip.archiveName} has been created"

This code creates a .zip file from the contents of source folder, which is within the folder where build.gradle is located. The name for the .zip file will be backupSet.zip, and will be placed in the same folder as build.gradle.

Before build execution ends, the .zip filename will be displayed in a message indicating that the file was created.

The output for customZip build

  1. The output for customZip build

Assigning a version number to the .zip file

Sometimes it’s useful to assign a version number to a .zip file, especially if the content of the file corresponds to an application’s source code, which is continuously modified.

To accomplish this task, a System property can be used in conjunction with the version property of the Zip type.

Code Listing  48

task versionedZip(type: Zip){

    from 'source'

    baseName = 'backupSet'

    version = System.properties['zip.version']

}

/* Using Groovy string interpolation to display zip file name. */

println "The ${versionedZip.archiveName} has been created"

The build should be executed with the following command.

Code Listing  49

gradle -Dzip.version=2.5 versionedZip

The zip file with a version number

  1. The zip file with a version number

Note: The expressions surrounded with the ${} placeholder in the double-quoted strings are part of the string interpolation action. This action evaluates the expression within the placeholder, and then transforms the result to its string representation upon evaluation of the entire string.

Chapter summary

As explained in Chapter 4, a task is the fundamental unit of build activity, and it’s identified with a name within the build script. This name is all the user needs to declare a task, but won’t produce any result because no action was assigned to the task itself.

The left-shift (<<) operator is used to bind a task to an action. An action consists of a set of statements within curly braces. Gradle also allows you to combine action code in the task by referring to the task object created with the task declaration. So, tasks are objects in Gradle programming.

When a code block is added to a task name with no left-shift operator (<<), this code block is treated as a configuration block. The code block will be run during Gradle’s configuration phase. This phase runs before the execution phase, when tasks’ actions are executed.

Every time Gradle executes a build, it creates an internal object model beforehand. Every task declared is a task object with properties and methods. The default type for each new task is DefaultTask, from which every Gradle task inherits. The control methods dependsOn(), doFirst(), doLast(), and onlyIf() were explained in this chapter, as well as the properties didWork, enabled, path, and description.

Besides the DefaultTask type, there are other special task types: the Copy and Zip types.

Finally, the term closure appeared in this chapter. This term is inherited from Groovy, and it refers to a block of code between two curly braces. You can think of a closure as an anonymous function. This block can work like an object, so it can be used as a parameter, or can be assigned to a variable to be executed later.

Scroll To Top
Disclaimer
DISCLAIMER: Web reader is currently in beta. Please report any issues through our support system. PDF and Kindle format files are also available for download.

Previous

Next



You are one step away from downloading ebooks from the Succinctly® series premier collection!
A confirmation has been sent to your email address. Please check and confirm your email subscription to complete the download.