Coding With Fun
Home Docker Django Node.js Articles Python pip guide FAQ Policy

The Gradle task is detailed


May 25, 2021 Gradle


Table of contents


Tasks are detailed

In Getting Started Tutorial Build Foundation, you've learned how to create simple tasks. Y ou've since learned how to add other behaviors to these tasks. A nd you've learned how to create dependencies between tasks. I t's all simple tasks. B ut Gradle makes the concept of the task more profound. G radle supports enhanced tasks, that is, tasks that have their own properties and methods. T his is really different from the Ant target you're using. This enhanced task can be provided by you or by Gradle.

Define the task

In building the foundation, we've seen how to define tasks in this style of keywords. I n some cases, you may need to use several different variants of this keyword style. For example, you cannot use this keyword style in an expression.

Define the task

build.gradle

task(hello) << {
    println "hello"
}
task(copy, type: Copy) {
    from(file('srcDir'))
    into(buildDir)
}  

You can also use strings as task names:

Define a task - use a string as the task name

build.gradle

task('hello') <<
{
    println "hello"
}
task('copy', type: Copy) {
    from(file('srcDir'))
    into(buildDir)
}  

For defining tasks, there is an alternative syntax that you might prefer to use:

Define tasks using alternative syntax

build.gradle

tasks.create(name: 'hello') << {
    println "hello"
}
tasks.create(name: 'copy', type: Copy) {
    from(file('srcDir'))
    into(buildDir)
}  

Here we add tasks to the task collection. For more changes to the create() method, take a look at TaskContainer.

Position the task

You often need to find the tasks you define in the build file, for example, in order to configure or rely on them. T here are many ways to do this. First, each task can be used as a property of the project, and the task name is used as the property name:

Access the task as a property

build.gradle

task hello
println hello.name
println project.hello.name  

Tasks can also be accessed through the task collection.

Access tasks through the task collection

build.gradle

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

From any project, you can use the task.getByPath() method to get the task path and access the task through it. You can call the getByPath() method with the task name, relative path, or absolute path as an argument.

Access tasks through a 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  

The output of the gradle -q hello

> gradle -q hello
:hello
:hello
:projectA:hello
:projectA:hello  

For more options for finding tasks, take a look at TaskContainer.

Configure the task

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

Create a replication task

build.gradle

task myCopy(type: Copy)  

The code above creates a copy task that does nothing. Y ou can use its API to configure this task (see Copy). The following example demonstrates several different ways to implement the same configuration.

Several ways to configure tasks

build.gradle

Copy myCopy = task(myCopy, type: Copy)
myCopy.from 'resources'
myCopy.into 'target'
myCopy.include('**/*.txt', '**/*.xml', '**/*.properties')  

This is similar to how we typically configure objects in Java. Y ou must repeat the context (myCopy) at each configuration statement. This is redundant and hard to read.

There is another way to configure tasks. I t also preserves context and is arguably the most readable. It's our usual favorite way.

Configure tasks - Use closures

build.gradle

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

This applies to any task. L ine 3 of the example is just a concise way to write the tasks.getByName() method. In particular, if you pass in a closure to the getByName() method, the closed app is configuring the task, not when it is executed.

You can also use a configuration closure when defining a task.

Define tasks using closures

build.gradle

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

Add dependencies to tasks

There are several ways to define a task's dependencies. I n Dependency Management Foundation, you've been introduced to using task names to define dependencies. T he name of a task can point to a task in the same project, or a task in another project. T o refer to a task in another project, you need to add the path of the project to which it belongs as a prefix to its name. Here's an example of adding dependencies from projectA:taskX to projectB:taskY:

Add dependencies from the tasks of another project

build.gradle

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

The output of the gradle -q taskX

> gradle -q taskX
taskY
taskX  

You can define dependencies using a Task object instead of a task name, as follows:

Use the task object to add dependencies

build.gradle

task taskX << {
    println 'taskX'
}
task taskY << {
    println 'taskY'
}
taskX.dependsOn taskY  

The output of the gradle -q taskX

> gradle -q taskX
taskY
taskX  

For more advanced usage, you can use closures to define task dependencies. W hen calculating a dependency, the closure is passed in to the task being calculated. T his closure should return a Task object or a collection of Task objects, and the return value will be used as a dependency on the task. The following example is to join the dependency from taskX on all tasks in the project that start with lib:

Use closures to add dependencies

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'
}  

The output of the gradle -q taskX

> gradle -q taskX
lib1
lib2
taskX  

For more information about task dependencies, see Task's API.

Task sorting

Task sorting is also an incubation feature. N ote that this feature may change in future Gradle releases.

In some cases, it is useful to control the order in which two tasks are executed without introducing explicit dependencies between those tasks. The main difference between task sorting and task dependency is that sorting rules do not affect the execution of those tasks, but only the order in which they are executed.

Task sorting can be useful in many cases:

  • Force task order execution: For example, 'build' is never executed before 'clean'.
  • Build validation early in the build: For example, verify that you have the correct certificate before you start publishing.
  • Get faster feedback by running quick validation before long-term validation: unit tests, for example, should run before integration testing.
  • A task aggregates the results of all tasks of a particular type: for example, a test reporting task combines the output of all test tasks performed.

Two sorting rules are available: Must Run After and Run After.

By using the "must run after" collation, you can specify that taskB must always run after taskA, regardless of when taskA and taskB tasks are scheduled to execute. T his is represented as taskB.mustRunAfter (taskA). T he "should run later" collation is similar, but less restrictive, because it is ignored in both cases. T he first is if you use this rule to introduce a sort loop. S econd, when parallel execution is used, and all the conditions for a task except that the task should run later are met, the task runs regardless of whether its "should run later" dependency is already running. When you prefer faster feedback, the "should run later" rule is used because this sorting is helpful but not strict.

It is still possible to use taskA execution without taskB execution, or taskB execution and taskA without execution.

Add a task sort that 'must run after'

build.gradle

task taskX << {
    println 'taskX'
}
task taskY << {
    println 'taskY'
}
taskY.mustRunAfter taskX  

The output of the gradle -q taskY taskX

> gradle -q taskY taskX
taskX
taskY  

Add a sort of task that 'should run later'

build.gradle

task taskX << {
    println 'taskX'
}
task taskY << {
    println 'taskY'
}
taskY.shouldRunAfter taskX  

The output of the gradle -q taskY taskX

> gradle -q taskY taskX
taskX
taskY  

In the example above, it is still possible to execute taskY without causing taskX to run as well:

Task sorting does not mean that the task is executed

The output of the gradle -q taskY

> gradle -q taskY
taskY  

If you want to specify the Must Run After and Run After sorts between two tasks, you can use the Task.mustRunAfter() and Task.shouldRunAfter() methods. These methods accept any other input accepted by a task instance, task name, or Task.dependsOn() as an argument.

Note that "B.mustRunAfter(A)" or "B.shouldRunAfter(A)" does not imply any execution dependencies between these tasks:

  • It can perform tasks A and B independently. Collations only work when both tasks are scheduled to be executed.
  • When the --continue parameter runs, it may be that B executes after A fails.

As mentioned earlier, if the "should run later" collation introduces a sort loop, it will be ignored.

When a loop is introduced, the task sort that "should run after it" is ignored

build.gradle

task taskX << {
    println 'taskX'
}
task taskY << {
    println 'taskY'
}
task taskZ << {
    println 'taskZ'
}
taskX.dependsOn taskY
taskY.dependsOn taskZ
taskZ.shouldRunAfter taskX  

The output of the gradle -q taskX

> gradle -q taskX
taskZ
taskY
taskX  

Add a description to the task

You can add a description to your task. For example, this description is displayed when the gradle tasks are executed.

Add a description to the task

build.gradle

task copy(type: Copy) {
   description 'Copies the resource directory to the target directory.'
   from 'resources'
   into 'target'
   include('**/*.txt', '**/*.xml', '**/*.properties')
}  

Replace the task

Sometimes you want to replace a task. F or example, you want to swap a task added through a Java plug-in with a different type of custom task. You can do this:

Rewrite the task

build.gradle

task copy(type: Copy)
task copy(overwrite: true) << {
    println('I am the new one.')
}  

The output of the gradle -q copy

> gradle -q copy
I am the new one.  

Here we replace a Copy-type task with a simple task. W hen you create this simple task, you must set the overwrite property to true. Otherwise, Gradle throws an exception that says the task for this name already exists.

Skip the task

Gradle provides a variety of ways to skip the execution of a task.

Use assertions

You can use the onlyIf() method to attach an assertion to a task. A n action that performs a task is performed only if the result is true. Y ou can implement an assertion with a closure. C losures are passed to the task as a parameter, and the task should return true when it is executed, or false when the task should be skipped. Assertions are calculated only before the task is executed.

Use assertions to skip a task

build.gradle

task hello << {
    println 'hello world'
}
hello.onlyIf { !project.hasProperty('skipHello') }  

The output of the gradle hello -PskipHello

> gradle hello -PskipHello
:hello SKIPPED
BUILD SUCCESSFUL
Total time: 1 secs  

Use StopExecutionException

If the rules that skip tasks cannot be expressed at the same time as assertions, you can use StopExecutionException. I f an action throws this exception, the next behavior of the action and the rest of the action of the task are skipped. The build continues with the next task.

Skip tasks with StopExecutionException

build.gradle

task compile << {
    println 'We are doing the compile.'
}
compile.doFirst {
    // Here you would put arbitrary conditions in real life. But we use this as an integration test, so we want defined behavior.
    if (true) { throw new StopExecutionException() }
}
task myTask(dependsOn: 'compile') << {
   println 'I am not affected'
}  

The output of the gradle -q myTask

> gradle -q myTask
I am not affected  

This feature is useful if you are using tasks provided by Gradle. It allows you to add execution conditions to a task's built-in operations.

Enable and disable tasks

Each task has an enabled tag with a default value of true. Set it to false to prevent any action from performing this task.

Enable and disable tasks

build.gradle

task disableMe << {
    println 'This should not be printed if the task is disabled.'
}
disableMe.enabled = false  

The output of Gradle disableMe

> gradle disableMe
:disableMe SKIPPED
BUILD SUCCESSFUL
Total time: 1 secs  

Skip tasks that are up-to-date

If you use tasks that come with Gradle, such as tasks added by the Java plug-in, you may have noticed that Gradle skips tasks that are up-to-date. This line also works on tasks that you define yourself, not just built-in tasks.

Declares the input and output of a task

Let's look at an example. H ere our task is to generate multiple output files from one XML source file. Let's run it a few times.

A build task

build.gradle

task transform {
    ext.srcFile = file('mountains.xml')
    ext.destDir = new File(buildDir, 'generated')
    doLast {
        println "Transforming source file."
        destDir.mkdirs()
        def mountains = new XmlParser().parse(srcFile)
        mountains.mountain.each { mountain ->
            def name = mountain.name[0].text()
            def height = mountain.height[0].text()
            def destFile = new File(destDir, "${name}.txt")
            destFile.text = "$name -> ${height}\n"
        }
    }
}  

The output of the gradle transform

> gradle transform
:transform
Transforming source file.  

The output of the gradle transform

> gradle transform
:transform
Transforming source file.  

Note that the second time Gradle performs this task, he does not skip the task, even if nothing has changed. O ur sample task is defined by an action closure. G radle doesn't know what the closure did, and can't automatically tell if the task is up-to-date. To check with Gradle's latest state (up-to-date), you need to declare the input and output of this task.

Each task has an input and outputs property that declares the input and output of the task. B elow, we modify our example to declare that it uses the XML source file as input and produces output to a target directory. Let's run it a few times.

Declares the input and output of a task

build.gradle

task transform {
    ext.srcFile = file('mountains.xml')
    ext.destDir = new File(buildDir, 'generated')
    inputs.file srcFile
    outputs.dir destDir
    doLast {
        println "Transforming source file."
        destDir.mkdirs()
        def mountains = new XmlParser().parse(srcFile)
        mountains.mountain.each { mountain ->
            def name = mountain.name[0].text()
            def height = mountain.height[0].text()
            def destFile = new File(destDir, "${name}.txt")
            destFile.text = "$name -> ${height}\n"
        }
    }
}  

The output of the gradle transform

> gradle transform
:transform
Transforming source file.  

The output of the gradle transform

> gradle transform
:transform UP-TO-DATE  

Now, Gradle knows which files to check to determine if the task is up-to-date.

The inputs property of the task is the TaskInputs type. The outputs property of the task is the TaskOutputs type.

A task without defined output will never be treated as up-to-date. For tasks where the output is not a file scenario, or a more complex scenario, the TaskOutputs.upToDateWhen() method allows you to programmatically calculate whether the output of a task should be judged to be up-to-date.

A task that defines only the output, and if its output has not changed since the last build, it will be rated as up-to-date.

How does it work?

Gradle snapshots the input before performing the first task. T his snapshot contains hash values for the input file set and the contents of each file. G radle then performs the task. I f the task completes successfully, Gradle will take a snapshot of the output. T he snapshot contains a hash of the output file set and the contents of each file. Gradle saves both snapshots until the next execution of the task.

Each time after that, Gradle takes a new snapshot of the inputs and outputs before performing the task. I f the new snapshot is the same as the previous snapshot, Gradle assumes that the output is up-to-date and skips the task. I f they are different, Gradle performs the task. Gradle saves both snapshots until the next execution of the task.

Note that if a task has a specified output directory, all files added to that directory after its last execution will be ignored and the task will not become obsolete. T his is an unrelated task that can share an output directory without interfering with each other. If you don't want to do this for some reason, consider using TaskOutputs.upToDateWhen()

Task rules

Sometimes you want to have a task that behaves like a large number or an infinite number of parameter numeric ranges. Task rules are a good way to provide such tasks:

Task rules

build.gradle

tasks.addRule("Pattern: ping<ID>") { String taskName ->
    if (taskName.startsWith("ping")) {
        task(taskName) << {
            println "Pinging: " + (taskName - 'ping')
        }
    }
}  

The output of Gradle q pingServer1

> gradle -q pingServer1
Pinging: Server1  

This string argument is used as a description of this rule. This description is displayed when the gradle tasks are run on this example.

Rules don't just call tasks from the command line to work. You can also create dependencies on rule-based tasks:

Rule-based task dependencies

build.gradle

tasks.addRule("Pattern: ping<ID>") { String taskName ->
    if (taskName.startsWith("ping")) {
        task(taskName) << {
            println "Pinging: " + (taskName - 'ping')
        }
    }
}
task groupPing {
    dependsOn pingServer1, pingServer2
}  

The output of Gradle q groupPing

> gradle -q groupPing
Pinging: Server1
Pinging: Server2  

The destructor task

The destructor task is a function in hatching. When the final task is ready to run, the destructor task is automatically added to the task diagram.

Add a destructor task

build.gradle

task taskX << {
    println 'taskX'
}
task taskY << {
    println 'taskY'
}
taskX.finalizedBy taskY  

The output of the gradle -q taskX

> gradle -q taskX
taskX
taskY  

The destroyer task is executed even if the final task fails.

The task destructor that performs the failed task

build.gradle

task taskX << {
    println 'taskX'
    throw new RuntimeException()
}
task taskY << {
    println 'taskY'
}
taskX.finalizedBy taskY  

The output of the gradle -q taskX

> gradle -q taskX
taskX
taskY  

On the other hand, if the final task does nothing, such as because of a failed task dependency or if it is considered up-to-date, the destructor task is not executed.

Destructing is considered useful in situations where the resources created must be cleaned up, whether the build succeeds or fails. An example of such a resource is a web container that starts before the integration test task and closes later, even if some tests fail.

You can specify a destructor task using the Task.finalizedBy() method. This method accepts as an <a4><c5>Task.dependsOn()</c5></a4> argument any other input accepted by a task instance, task name, or .

Summarize

If you're from Ant, an enhanced Gradle task like Copy looks like a mixture of an Ant target and an Ant task. T hat's actually true. G radle does not separate tasks and targets like Ant does. S imple Gradle tasks are like Ant's goals, while enhanced Gradle tasks include aspects of Ant tasks. A ll of Gradle's tasks share a common API, and you can create dependencies between them. S uch a task might be better configured than an Ant task. It takes full advantage of the type system, is more expressive and easy to maintain.