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. Defining tasks
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. Defining tasks - using strings
build.gradle
task('hello') << { println "hello" } task('copy', type: Copy) { from(file('srcDir')) into(buildDir) }
There is an alternative syntax for defining tasks, which you may prefer to use:
Example 12.3. Defining tasks with alternative syntax
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:
Example 12.4. Accessing tasks as properties
build.gradle
task hello println hello.name println project.hello.name
Tasks are also available through the tasks
collection.
Example 12.5. Accessing tasks via tasks collection
build.gradle
task hello println tasks.hello.name println tasks['hello'].name
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. Accessing tasks by path
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').path
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:
[12]
This creates a copy task with no default behavior.
The task can be configured using its API (see
Copy
).
The following examples show several different ways to achieve the same configuration.
Example 12.8. Configuring a task - various ways
build.gradle
Copy myCopy = task(myCopy, type: Copy) myCopy.from 'resources' myCopy.into 'target' myCopy.include('**/*.txt', '**/*.xml', '**/*.properties')
This is similar to the way we would normally configure objects in Java. You have to repeat the context
(myCopy
) 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.9. Configuring a task - fluent interface
build.gradle
task(myCopy, type: Copy) .from('resources') .into('target') .include('**/*.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 include
methods all return an object that may be used to chain to additional configuration methods. 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.10. Configuring a task - with closure
build.gradle
task myCopy(type: Copy) myCopy { from 'resources' into 'target' include('**/*.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.11. Configuring a task - with configure() method
build.gradle
task myCopy(type: Copy) myCopy.configure { from('source') into('target') include('**/*.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.12. Defining a task with closure
build.gradle
task copy(type: Copy) { from 'resources' into 'target' include('**/*.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.13. Adding dependency on task from another project
build.gradle
project('projectA') { task taskX(dependsOn: ':projectB:taskY') << { println 'taskX' } } project('projectB') { task taskY << { println 'taskY' } }
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.14. Adding dependency using task object
build.gradle
task taskX << { println 'taskX' } task taskY << { println 'taskY' } taskX.dependsOn taskY
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.15. Adding dependency using closure
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' }
Output of gradle -q taskX
> gradle -q taskX lib1 lib2 taskX
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.16. Adding a description to a task
build.gradle
task copy(type: Copy) { description = 'Copies the resource directory to the target directory.' from 'resources' into 'target' include('**/*.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.17. Overwriting a task
build.gradle
task copy(type: Copy) task copy(overwrite: true) << { println('I am the new one.') }
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.
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.18. Task rule
build.gradle
tasks.addRule("Pattern: ping<ID>") { String taskName -> if (taskName.startsWith("ping")) { task(taskName) << { println "Pinging: " + (taskName - 'ping') } } }
Output of gradle -q pingServer1
> gradle -q pingServer1 Pinging: Server1
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.19. Dependency on rule based tasks
build.gradle
tasks.addRule("Pattern: ping<ID>") { String taskName -> if (taskName.startsWith("ping")) { task(taskName) << { println "Pinging: " + (taskName - 'ping') } } } task groupPing { dependsOn pingServer1, pingServer2 }
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.