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.
We have already seen how to define tasks using a keyword style in Chapter 4, Build Script Basics. There are a few variations on this style, which you may need to use in certain situations. For example, the keyword style does not work in expressions.
Example 12.1.
build.gradle
task(hello) << {
println "hello"
}
task(copy, type: Copy) {
from(file('srcDir'))
into(buildDir)
}You can also use strings for the task names:
Example 12.2.
build.gradle
task('hello') <<
{
println "hello"
}
task('copy', type: Copy) {
from(file('srcDir'))
into(buildDir)
}There is an alternate syntax for defining tasks, which you may prefer to use:
Example 12.3.
build.gradle
tasks.add(name: 'hello') << {
println "hello"
}
tasks.add(name: 'copy', type: Copy) {
from(file('srcDir'))
into(buildDir)
}Here we add tasks to the tasks collection. Have a look at
TaskContainer
for more variations of the add()
method.
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:
Tasks are also available through the tasks collection.
You can access tasks from any project using the task's path using the tasks.getByPath()
method. You can call the getByPath() method with a task name, or a relative path, or an
absolute path.
Example 12.6.
build.gradle
project(':projectA') {
task hello
}
task hello
println tasks.getByPath('hello').path
println tasks.getByPath(':hello').path
println tasks.getByPath('projectA:hello').path
println tasks.getByPath(':projectA:hello').pathExample 12.7. Output of gradle -q hello
> gradle -q hello :hello :hello :projectA:hello :projectA:hello
Have a look at
TaskContainer
for more options for locating 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:
[13]
The copy task provides an API to configure it (see
Copy
).
If you create the copy task like this, it has no default behavior.
[14]
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.9.
build.gradle
Copy copy = 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
(copy) 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.10.
build.gradle
task(copy, type: 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.11.
build.gradle
task copy(type: Copy)
copy {
from(file('resources'))
into(file('target'))
includes('**/*.txt', '**/*.xml', '**/*.properties')
}This works for any task. Line 3 of the example is just a shortcut for the
tasks.getByName() method. It is important to note that if you pass a closure to the
getByName() method, this closure is applied to configure the task.
There is a slightly different ways of doing this.
Example 12.12.
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.
You can also use a configuration closure when you define a task.
Example 12.13.
build.gradle
task copy(type: Copy) {
from(file('resources'))
into(file('target'))
includes('**/*.txt', '**/*.xml', '**/*.properties')
}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.14.
build.gradle
project('projectA') {
task taskX(dependsOn: ':projectB:taskY') << {
println 'taskX'
}
}
project('projectB') {
task taskY << {
println 'taskY'
}
}Instead of using a task name, you can define a dependency using a
Task object, as shown in this example:
Example 12.16.
build.gradle
task taskX << {
println 'taskX'
}
task taskY << {
println 'taskY'
}
taskX.dependsOn taskYFor 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.18.
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'
}For more information about task dependencies, see the
Task
API.
You can add a description to your task. This description is for example displayed when executing
gradle -t.
Example 12.20.
build.gradle
task copy(type: Copy) {
description = 'Copies the resource directory to the target directory.'
from(file('resources'))
into(file('target'))
includes('**/*.txt', '**/*.xml', '**/*.properties')
}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.21.
build.gradle
task copy(type: Copy)
task copy(overwrite: true) << {
println('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.
Sometimes you want to have a task which behavior depends on a large or infinite number value range of parameters. A very nice and expressive way to provide such tasks are task rules:
Example 12.23.
build.gradle
tasks.addRule("Pattern: ping<ID>") { String taskName ->
if (taskName.startsWith("ping")) {
task(taskName) << {
println "Pinging: " + (taskName - 'ping')
}
}
}The String parameter is used as a description for the rule. This description is shown when doing
for example gradle -t.
Rules not just work for calling tasks from the command line. You can also create dependsOn relations on rule based tasks:
Example 12.25.
build.gradle
tasks.addRule("Pattern: ping<ID>") { String taskName ->
if (taskName.startsWith("ping")) {
task(taskName) << {
println "Pinging: " + (taskName - 'ping')
}
}
}
task groupPing {
dependsOn pingServer1, pingServer2
}Example 12.26. Output of gradle -q groupPing
> gradle -q groupPing Pinging: Server1 Pinging: Server2
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.