Chapter 12. More about Tasks

In the introductory tutorial (Chapter 4, Build Script Basics) you have learned how to create simple tasks. You have also learned how to add additional behavior to these tasks later on. And you have learned how to create dependencies between tasks. This was all about simple tasks. But Gradle takes the concept of tasks further. Gradle supports enhanced tasks, that is, tasks which have their own properties and methods. This is really different to what you are used to with Ant targets. Such enhanced tasks are either provided by you or are provided by Gradle.

12.1. Defining Tasks

We have already seen how to define tasks using a keyword style in Chapter 4, Build Script Basics. There is an alternate syntax for defining tasks:

Example 12.1.  build.gradle

tasks.add(name: 'hello') {
    println "hello"
}

tasks.add(name: 'copy', type: Copy)

Here we add tasks to the tasks collection. Have a look at TaskContainer for more variations of the add() method.

12.2. Locating Tasks

You often need to locate the tasks that you have defined in the build file, for example, to configure them or use them for dependencies. There are a number of ways you can do this. Firstly, each task is available as a property of the project, using the task name as the property name:

Example 12.2.  build.gradle

task hello

println hello.name
println project.hello.name

Tasks are also available through the tasks collection.

Example 12.3.  build.gradle

task hello

println tasks.hello.name
println tasks['hello'].name

You can access tasks from any project using the task's path with the task() method. You can call the task() method with a task name, or a relative path, or an absolute path.

Example 12.4.  build.gradle

project(':projectA') {
    task hello
}

task hello

println task('hello').path
println task(':hello').path
println task('projectA:hello').path
println task(':projectA:hello').path

Have a look at TaskContainer for more options for locating tasks.

12.3. Configuring Tasks

As an example, let's look at the Copy task provided by Gradle. To create a Copy task for your build, you can declare in your build script: [15]

Example 12.5.  build.gradle

task copy(type: Copy)

The copy task provides an API to configure it (see Copy ). If you create the copy task like this, it has no default behavior. [16] We want now use the copy task to learn about different ways to configure tasks. The examples below all show different ways to achieve the same configuration.

Example 12.6.  build.gradle

task copy(type: Copy)
copy.from(file('resources'))
copy.into(file('target'))
copy.includes('**/*.txt', '**/*.xml', '**/*.properties')

This is similar to the way we would normally configure objects in Java. You have to repeat the context (copyTask) in the configuration statement every time. This is a redundancy and not very nice to read.

There is a more convenient way of doing this.

Example 12.7.  build.gradle

task copy(type: Copy)
copy.from(file('resources'))
        .into(file('target'))
        .includes('**/*.txt', '**/*.xml', '**/*.properties')

You might know this approach from the Hibernates Criteria Query API or JMock. Of course the API of a task has to support this. The from, to and includes methods all return an instance of the Copy object. Gradle's build-in tasks usually support this configuration style.

But there is yet another way of configuring a task. It also preserves the context and it is arguably the most readable. It is usually our favorite.

Example 12.8.  build.gradle

task copy(type: Copy)
copy {
   from(file('resources'))
   into(file('target'))
   includes('**/*.txt', '**/*.xml', '**/*.properties')
}

This works for any task. Line 2 of the example is just a shortcut for the task() method. It is important to note that if you pass a closure to the task() method, this closure is applied to configure the task.

There is a slightly different ways of doing this.

Example 12.9.  build.gradle

task copy(type: Copy)
copy.configure {
   from(file('source'))
   into(file('target'))
   includes('**/*.txt', '**/*.xml', '**/*.properties')
}

Every task has a configure() method, which you can pass a closure for configuring the task. Gradle uses this style for configuring objects in many places, not just for tasks.

12.4. Adding Dependencies to Tasks

There are several ways you can define the dependencies of a task. In Section 4.3, “Task dependencies” you were introduced to defining dependencies using task names. Task names can refer to tasks in the same project as the task, or to tasks in other projects. To refer to a task in another project, you prefix the name of the task with the path of the project it belongs to. Below is an example which adds a dependency from projectA:taskX to projectB:taskY:

Example 12.10.  build.gradle

project('projectA') {
    task taskX(dependsOn: ':projectB:taskY') {
        println 'taskX'
    }
}

project('projectB') {
    task taskY {
        println 'taskY'
    }
}

Example 12.11. Output of gradle -q taskX

> gradle -q taskX
taskY
taskX

Instead of using a task name, you can define a dependency using a Task object, as shown in this example:

Example 12.12.  build.gradle

task taskX {
    println 'taskX'
}

task taskY {
    println 'taskY'
}

taskX.dependsOn taskY

Example 12.13. Output of gradle -q taskX

> gradle -q taskX
taskY
taskX

For more advanced uses, you can define a task dependency using a closure. When evaluated, the closure is passed the task whose dependencies are being calculated. The closure should return a single Task or collection of Task objects, which are then treated as dependencies of the task. The following example adds a dependency from taskX to all the tasks in the project whose name starts with lib:

Example 12.14.  build.gradle

task taskX {
    println 'taskX'
}

taskX.dependsOn {
    tasks.findAll { task -> task.name.startsWith('lib') }
}

task lib1 {
    println 'lib1'
}

task lib2 {
    println 'lib2'
}

task notALib {
    println 'notALib'
}

Example 12.15. Output of gradle -q taskX

> gradle -q taskX
lib1
lib2
taskX

For more information about task dependencies, see the Task API.

12.5. Replacing Tasks

Sometimes you want to replace a task. For example if you want to exchange a task added by the Java Plugin with a custom task of a different type. You can achieve this with:

Example 12.16.  build.gradle

createTask('copy', type: Copy)

createTask('copy', overwrite: true) {
    println('I am the new one.')
}

Example 12.17. Output of gradle -q copy

> gradle -q copy
I am the new one.

Here we replace a task of type Copy with a simple task. When creating the simple task, you have to set the overwrite property to true. Otherwise Gradle throws an exception, saying that a task with such a name already exists.

12.6. Summary

If you are coming from Ant, such an enhanced Gradle task as Copy looks like a mixture between an Ant target and an Ant task. And this is actually the case. The separation that Ant does between tasks and targets is not done by Gradle. The simple Gradle tasks are like Ant's targets and the enhanced Gradle tasks also include the Ant task aspects. All of Gradle's tasks share a common API and you can create dependencies between them. Such a task might be nicer to configure than an Ant task. It makes full use of the type system, is more expressive and easier to maintain.



[15] If you use the Java Plugin, this task is automatically created and added to your project.

[16] This is different when added by the Java Plugin where it is used for resource processing.