# Authoring Tasks

version 8.4

In the introductory tutorial you learned how to create simple tasks. You also learned how to add additional behavior to these tasks later on, and you learned how to create dependencies between tasks. This was all about simple tasks, but Gradle takes the concept of tasks further. Gradle supports tasks that have their own properties and methods. Such tasks are either provided by you or built into Gradle.

## Task outcomes

When Gradle executes a task, it can label the task with different outcomes in the console UI and via the Tooling API. These labels are based on if a task has actions to execute, if it should execute those actions, if it did execute those actions and if those actions made any changes.

`(no label)` or `EXECUTED`

  Task executed its actions.

  - Task has actions and Gradle has determined they should be executed as part of a build.

  - Task has no actions and some dependencies, and any of the dependencies are executed. See also Lifecycle Tasks.

`UP-TO-DATE`

  Task’s outputs did not change.

  - Task has outputs and inputs and they have not changed. See Incremental Build.

  - Task has actions, but the task tells Gradle it did not change its outputs.

  - Task has no actions and some dependencies, but all of the dependencies are up-to-date, skipped or from cache. See also Lifecycle Tasks.

  - Task has no actions and no dependencies.

`FROM-CACHE`

  Task’s outputs could be found from a previous execution.

  - Task has outputs restored from the build cache. See Build Cache.

`SKIPPED`

  Task did not execute its actions.

  - Task has been explicitly excluded from the command-line. See Excluding tasks from execution.

  - Task has an `onlyIf` predicate return false. See Using a predicate.

`NO-SOURCE`

  Task did not need to execute its actions.

  - Task has inputs and outputs, but no sources. For example, source files are `.java` files for JavaCompile.

## Defining tasks

We have already seen how to define tasks using strings for task names in this chapter. There are a few variations on this style, which you may need to use in certain situations.

| The task configuration APIs are described in more detail in the task configuration avoidance chapter.

Example 1. Defining tasks using strings for task names
build.gradle.kts

    tasks.register("hello") {
        doLast {
            println("hello")
        }
    }

    tasks.register<Copy>("copy") {
        from(file("srcDir"))
        into(buildDir)
    }

build.gradle

    tasks.register('hello') {
        doLast {
            println 'hello'
        }
    }

    tasks.register('copy', Copy) {
        from(file('srcDir'))
        into(buildDir)
    }

We add the tasks to the `tasks` collection. Have a look at TaskContainer for more variations of the `register()` method.

In the Kotlin DSL there is also a specific delegated properties syntax that is useful if you need the registered task for further reference.

Example 2. Assigning tasks to variables with DSL specific syntax
build.gradle.kts

    // Using Kotlin delegated properties

    val hello by tasks.registering {
        doLast {
            println("hello")
        }
    }

    val copy by tasks.registering(Copy::class) {
        from(file("srcDir"))
        into(buildDir)
    }

build.gradle

    // Assigning registered tasks to a variable in Groovy

    def hello = tasks.register('hello') {
        doLast {
            println 'hello'
        }
    }

    def copy = tasks.register('copy', Copy) {
        from(file('srcDir'))
        into(buildDir)
    }

| If you look at the API of the tasks container you may notice that there are additional methods to create tasks. The use of these methods is discouraged and will be deprecated in future versions. These methods only exist for backward compatibility as they were introduced before task configuration avoidance was added to Gradle.

## 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 of doing this. Firstly, just like with defining tasks there are language specific syntaxes for the Groovy and Kotlin DSL:

In general, tasks are available through the `tasks` collection. You should use the methods that return a task provider – `register()` or `named()` – to make sure you do not break task configuration avoidance.

Example 3. Accessing tasks via tasks collection
build.gradle.kts

    tasks.register("hello")
    tasks.register<Copy>("copy")

    println(tasks.named("hello").get().name) // or just 'tasks.hello' if the task was added by a plugin

    println(tasks.named<Copy>("copy").get().destinationDir)

build.gradle

    tasks.register('hello')
    tasks.register('copy', Copy)

    println tasks.named('hello').get().name

    println tasks.named('copy').get().destinationDir

Tasks of a specific type can also be accessed by using the `tasks.withType()` method. This enables to easily avoid duplication of code and reduce redundancy.

Example 4. Accessing tasks by their type
build.gradle.kts

    tasks.withType<Tar>().configureEach {
        enabled = false
    }

    tasks.register("test") {
        dependsOn(tasks.withType<Copy>())
    }

build.gradle

    tasks.withType(Tar).configureEach {
        enabled = false
    }

    tasks.register('test') {
        dependsOn tasks.withType(Copy)
    }

| The following shows how to access a task by path. This is not a recommended practice anymore as it breaks task configuration avoidance and project isolation. Dependencies between projects should be declared as dependencies.

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 5. Accessing tasks by path
project-a/build.gradle.kts

    tasks.register("hello")

build.gradle.kts

    tasks.register("hello")

    println(tasks.getByPath("hello").path)
    println(tasks.getByPath(":hello").path)
    println(tasks.getByPath("project-a:hello").path)
    println(tasks.getByPath(":project-a:hello").path)

project-a/build.gradle

    tasks.register('hello')

build.gradle

    tasks.register('hello')

    println tasks.getByPath('hello').path
    println tasks.getByPath(':hello').path
    println tasks.getByPath('project-a:hello').path
    println tasks.getByPath(':project-a:hello').path

Output of `gradle -q hello`

    > gradle -q hello
    :hello
    :hello
    :project-a:hello
    :project-a:hello

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

## Configuring tasks

As an example, let’s look at the `Copy` task provided by Gradle. To register a `Copy` task for your build, you can declare in your build script:

Example 6. Registering a copy task
build.gradle.kts

    tasks.register<Copy>("myCopy")

build.gradle

    tasks.register('myCopy', Copy)

This registers 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.

Just to be clear, realize that the name of this task is `myCopy`, but it is of type `Copy`. You can have multiple tasks of the same type, but with different names. You’ll find this gives you a lot of power to implement cross-cutting concerns across all tasks of a particular type.

Example 7. Configuring a task
build.gradle.kts

    tasks.named<Copy>("myCopy") {
        from("resources")
        into("target")
        include("**/*.txt", "**/*.xml", "**/*.properties")
    }

build.gradle

    tasks.named('myCopy') {
        from 'resources'
        into 'target'
        include('**/*.txt', '**/*.xml', '**/*.properties')
    }

You can also store the task reference in a variable and use to configure the task further at a later point in the script.

Example 8. Retrieve a task reference and use it to configuring the task
build.gradle.kts

    // Configure task using Kotlin delegated properties and a lambda
    val myCopy by tasks.existing(Copy::class) {
        from("resources")
        into("target")
    }
    myCopy {
        include("**/*.txt", "**/*.xml", "**/*.properties")
    }

build.gradle

    // Configure task through a task provider
    def myCopy = tasks.named('myCopy')  {
        from 'resources'
        into 'target'
    }
    myCopy.configure {
        include('**/*.txt', '**/*.xml', '**/*.properties')
    }

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

| If you use the Kotlin DSL and the task you want to configure was added by a plugin, you can use a convenient accessor for the task. That is, instead of `tasks.named("test")` you can just write `tasks.test`.

You can also use a configuration block when you define a task.

Example 9. Defining a task with a configuration block
build.gradle.kts

    tasks.register<Copy>("copy") {
       from("resources")
       into("target")
       include("**/*.txt", "**/*.xml", "**/*.properties")
    }

build.gradle

    tasks.register('copy', Copy) {
       from 'resources'
       into 'target'
       include('**/*.txt', '**/*.xml', '**/*.properties')
    }

| Don’t forget about the build phases A task has both configuration and actions. When using the `doLast`, you are simply using a shortcut to define an action. Code defined in the configuration section of your task will get executed during the configuration phase of the build regardless of what task was targeted. See Build Lifecycle for more details about the build lifecycle.

## Passing arguments to a task constructor

As opposed to configuring the mutable properties of a `Task` after creation, you can pass argument values to the `Task` class’s constructor. In order to pass values to the `Task` constructor, you must annotate the relevant constructor with `@javax.inject.Inject`.

Example 10. Task class with `@Inject` constructor
build.gradle.kts

    abstract class CustomTask @Inject constructor(
        private val message: String,
        private val number: Int
    ) : DefaultTask()

build.gradle

    abstract class CustomTask extends DefaultTask {
        private final String message
        private final int number

        @Inject
        CustomTask(String message, int number) {
            this.message = message
            this.number = number
        }
    }

You can then create a task, passing the constructor arguments at the end of the parameter list.

Example 11. Registering a task with constructor arguments using TaskContainer
build.gradle.kts

    tasks.register<CustomTask>("myTask", "hello", 42)

build.gradle

    tasks.register('myTask', CustomTask, 'hello', 42)

| It’s recommended to use the Task Configuration Avoidance APIs to improve configuration time.

In all circumstances, the values passed as constructor arguments must be non-null. If you attempt to pass a `null` value, Gradle will throw a `NullPointerException` indicating which runtime value is `null`.

## Adding dependencies to a task

There are several ways you can define the dependencies of a task. In 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. The following is an example which adds a dependency from `project-a:taskX` to `project-b:taskY`:

Example 12. Adding dependency on task from another project
build.gradle.kts

    project("project-a") {
        tasks.register("taskX") {
            dependsOn(":project-b:taskY")
            doLast {
                println("taskX")
            }
        }
    }

    project("project-b") {
        tasks.register("taskY") {
            doLast {
                println("taskY")
            }
        }
    }

build.gradle

    project('project-a') {
        tasks.register('taskX')  {
            dependsOn ':project-b:taskY'
            doLast {
                println 'taskX'
            }
        }
    }

    project('project-b') {
        tasks.register('taskY') {
            doLast {
                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 `TaskProvider` object, as shown in this example:

Example 13. Adding dependency using task provider object
build.gradle.kts

    val taskX by tasks.registering {
        doLast {
            println("taskX")
        }
    }

    val taskY by tasks.registering {
        doLast {
            println("taskY")
        }
    }

    taskX {
        dependsOn(taskY)
    }

build.gradle

    def taskX = tasks.register('taskX') {
        doLast {
            println 'taskX'
        }
    }

    def taskY = tasks.register('taskY') {
        doLast {
            println 'taskY'
        }
    }

    taskX.configure {
        dependsOn taskY
    }

Output of `gradle -q taskX`

    > gradle -q taskX
    taskY
    taskX

For more advanced uses, you can define a task dependency using a lazy block. When evaluated, the block is passed the task whose dependencies are being calculated. The lazy block 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 14. Adding dependency using a lazy block
build.gradle.kts

    val taskX by tasks.registering {
        doLast {
            println("taskX")
        }
    }

    // Using a Gradle Provider
    taskX {
        dependsOn(provider {
            tasks.filter { task -> task.name.startsWith("lib") }
        })
    }

    tasks.register("lib1") {
        doLast {
            println("lib1")
        }
    }

    tasks.register("lib2") {
        doLast {
            println("lib2")
        }
    }

    tasks.register("notALib") {
        doLast {
            println("notALib")
        }
    }

build.gradle

    def taskX = tasks.register('taskX') {
        doLast {
            println 'taskX'
        }
    }

    // Using a Gradle Provider
    taskX.configure {
        dependsOn(provider {
            tasks.findAll { task -> task.name.startsWith('lib') }
        })
    }

    tasks.register('lib1') {
        doLast {
            println('lib1')
        }
    }

    tasks.register('lib2') {
        doLast {
            println('lib2')
        }
    }

    tasks.register('notALib') {
        doLast {
            println('notALib')
        }
    }

Output of `gradle -q taskX`

    > gradle -q taskX
    lib1
    lib2
    taskX

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

## Ordering tasks

In some cases it is useful to control the order in which 2 tasks will execute, without introducing an explicit dependency between those tasks. The primary difference between a task ordering and a task dependency is that an ordering rule does not influence which tasks will be executed, only the order in which they will be executed.

Task ordering can be useful in a number of scenarios:

- Enforce sequential ordering of tasks: e.g. 'build' never runs before 'clean'.

- Run build validations early in the build: e.g. validate I have the correct credentials before starting the work for a release build.

- Get feedback faster by running quick verification tasks before long verification tasks: e.g. unit tests should run before integration tests.

- A task that aggregates the results of all tasks of a particular type: e.g. test report task combines the outputs of all executed test tasks.

There are two ordering rules available: “must run after” and “should run after”.

When you use the “must run after” ordering rule you specify that `taskB` must always run after `taskA`, whenever both `taskA` and `taskB` will be run. This is expressed as `taskB.mustRunAfter(taskA)`. The “should run after” ordering rule is similar but less strict as it will be ignored in two situations. Firstly if using that rule introduces an ordering cycle. Secondly when using parallel execution and all dependencies of a task have been satisfied apart from the “should run after” task, then this task will be run regardless of whether its “should run after” dependencies have been run or not. You should use “should run after” where the ordering is helpful but not strictly required.

With these rules present it is still possible to execute `taskA` without `taskB` and vice-versa.

Example 15. Adding a 'must run after' task ordering
build.gradle.kts

    val taskX by tasks.registering {
        doLast {
            println("taskX")
        }
    }
    val taskY by tasks.registering {
        doLast {
            println("taskY")
        }
    }
    taskY {
        mustRunAfter(taskX)
    }

build.gradle

    def taskX = tasks.register('taskX') {
        doLast {
            println 'taskX'
        }
    }
    def taskY = tasks.register('taskY') {
        doLast {
            println 'taskY'
        }
    }
    taskY.configure {
        mustRunAfter taskX
    }

Output of `gradle -q taskY taskX`

    > gradle -q taskY taskX
    taskX
    taskY

Example 16. Adding a 'should run after' task ordering
build.gradle.kts

    val taskX by tasks.registering {
        doLast {
            println("taskX")
        }
    }
    val taskY by tasks.registering {
        doLast {
            println("taskY")
        }
    }
    taskY {
        shouldRunAfter(taskX)
    }

build.gradle

    def taskX = tasks.register('taskX') {
        doLast {
            println 'taskX'
        }
    }
    def taskY = tasks.register('taskY') {
        doLast {
            println 'taskY'
        }
    }
    taskY.configure {
        shouldRunAfter taskX
    }

Output of `gradle -q taskY taskX`

    > gradle -q taskY taskX
    taskX
    taskY

In the examples above, it is still possible to execute `taskY` without causing `taskX` to run:

Example 17. Task ordering does not imply task execution
Output of `gradle -q taskY`

    > gradle -q taskY
    taskY

To specify a “must run after” or “should run after” ordering between 2 tasks, you use the Task.mustRunAfter(java.lang.Object...) and Task.shouldRunAfter(java.lang.Object...) methods. These methods accept a task instance, a task name or any other input accepted by Task.dependsOn(java.lang.Object...).

Note that “`B.mustRunAfter(A)`” or “`B.shouldRunAfter(A)`” does not imply any execution dependency between the tasks:

- It is possible to execute tasks `A` and `B` independently. The ordering rule only has an effect when both tasks are scheduled for execution.

- When run with `--continue`, it is possible for `B` to execute in the event that `A` fails.

As mentioned before, the “should run after” ordering rule will be ignored if it introduces an ordering cycle:

Example 18. A 'should run after' task ordering is ignored if it introduces an ordering cycle
build.gradle.kts

    val taskX by tasks.registering {
        doLast {
            println("taskX")
        }
    }
    val taskY by tasks.registering {
        doLast {
            println("taskY")
        }
    }
    val taskZ by tasks.registering {
        doLast {
            println("taskZ")
        }
    }
    taskX { dependsOn(taskY) }
    taskY { dependsOn(taskZ) }
    taskZ { shouldRunAfter(taskX) }

build.gradle

    def taskX = tasks.register('taskX') {
        doLast {
            println 'taskX'
        }
    }
    def taskY = tasks.register('taskY') {
        doLast {
            println 'taskY'
        }
    }
    def taskZ = tasks.register('taskZ') {
        doLast {
            println 'taskZ'
        }
    }
    taskX.configure { dependsOn(taskY) }
    taskY.configure { dependsOn(taskZ) }
    taskZ.configure { shouldRunAfter(taskX) }

Output of `gradle -q taskX`

    > gradle -q taskX
    taskZ
    taskY
    taskX

## Adding a description to a task

You can add a description to your task. This description is displayed when executing `gradle tasks`.

Example 19. Adding a description to a task
build.gradle.kts

    tasks.register<Copy>("copy") {
       description = "Copies the resource directory to the target directory."
       from("resources")
       into("target")
       include("**/*.txt", "**/*.xml", "**/*.properties")
    }

build.gradle

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

## Skipping tasks

Gradle offers multiple ways to skip the execution of a task.

### Using a predicate

You can use `Task.onlyIf` to attach a predicate to a task. The task’s actions are only executed if the predicate evaluates to true. The predicate is passed the task as a parameter, and should return true if the task should execute and false if the task should be skipped. The predicate is evaluated just before the task is executed. Passing an optional reason string to `onlyIf()` is useful for explaining why the task is skipped.

Example 20. Skipping a task using a predicate
build.gradle.kts

    val hello by tasks.registering {
        doLast {
            println("hello world")
        }
    }

    hello {
        val skipProvider = providers.gradleProperty("skipHello")
        onlyIf("there is no property skipHello") {
            !skipProvider.isPresent()
        }
    }

build.gradle

    def hello = tasks.register('hello') {
        doLast {
            println 'hello world'
        }
    }

    hello.configure {
        def skipProvider = providers.gradleProperty("skipHello")
        onlyIf("there is no property skipHello") {
            !skipProvider.present
        }
    }

Output of `gradle hello -PskipHello`

    > gradle hello -PskipHello
    > Task :hello SKIPPED

    BUILD SUCCESSFUL in 0s

It is possible to find the reason for a task being skipped by running the build with the `--info` logging level.

Output of `gradle hello -PskipHello --hello`

    > gradle hello -PskipHello --info
    ...

    > Task :hello SKIPPED
    Skipping task ':hello' as task onlyIf 'there is no property skipHello' is false.
    :hello (Thread[included builds,5,main]) completed. Took 0.018 secs.

    BUILD SUCCESSFUL in 13s

### Using StopExecutionException

If the logic for skipping a task can’t be expressed with a predicate, you can use the StopExecutionException. If this exception is thrown by an action, the further execution of this action as well as the execution of any following action of this task is skipped. The build continues with executing the next task.

Example 21. Skipping tasks with StopExecutionException
build.gradle.kts

    val compile by tasks.registering {
        doLast {
            println("We are doing the compile.")
        }
    }

    compile {
        doFirst {
            // Here you would put arbitrary conditions in real life.
            if (true) {
                throw StopExecutionException()
            }
        }
    }
    tasks.register("myTask") {
        dependsOn(compile)
        doLast {
            println("I am not affected")
        }
    }

build.gradle

    def compile = tasks.register('compile') {
        doLast {
            println 'We are doing the compile.'
        }
    }

    compile.configure {
        doFirst {
            // Here you would put arbitrary conditions in real life.
            if (true) {
                throw new StopExecutionException()
            }
        }
    }
    tasks.register('myTask') {
        dependsOn('compile')
        doLast {
            println 'I am not affected'
        }
    }

Output of `gradle -q myTask`

    > gradle -q myTask
    I am not affected

This feature is helpful if you work with tasks provided by Gradle. It allows you to add conditional execution of the built-in actions of such a task.[1]

### Enabling and disabling tasks

Every task has an `enabled` flag which defaults to `true`. Setting it to `false` prevents the execution of any of the task’s actions. A disabled task will be labelled SKIPPED.

Example 22. Enabling and disabling tasks
build.gradle.kts

    val disableMe by tasks.registering {
        doLast {
            println("This should not be printed if the task is disabled.")
        }
    }

    disableMe {
        enabled = false
    }

build.gradle

    def disableMe = tasks.register('disableMe') {
        doLast {
            println 'This should not be printed if the task is disabled.'
        }
    }

    disableMe.configure {
        enabled = false
    }

Output of `gradle disableMe`

    > gradle disableMe
    > Task :disableMe SKIPPED

    BUILD SUCCESSFUL in 0s

### Task timeouts

Every task has a `timeout` property which can be used to limit its execution time. When a task reaches its timeout, its task execution thread is interrupted. The task will be marked as failed. Finalizer tasks will still be run. If `--continue` is used, other tasks can continue running after it. Tasks that don’t respond to interrupts can’t be timed out. All of Gradle’s built-in tasks respond to timeouts in a timely manner.

Example 23. Specifying task timeouts
build.gradle.kts

    tasks.register("hangingTask") {
        doLast {
            Thread.sleep(100000)
        }
        timeout = Duration.ofMillis(500)
    }

build.gradle

    tasks.register("hangingTask") {
        doLast {
            Thread.sleep(100000)
        }
        timeout = Duration.ofMillis(500)
    }

## Task rules

Sometimes you want to have a task whose 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 24. Task rule
build.gradle.kts

    tasks.addRule("Pattern: ping<ID>") {
        val taskName = this
        if (startsWith("ping")) {
            task(taskName) {
                doLast {
                    println("Pinging: " + (taskName.replace("ping", "")))
                }
            }
        }
    }

build.gradle

    tasks.addRule("Pattern: ping<ID>") { String taskName ->

        if (taskName.startsWith("ping")) {
            task(taskName) {
                doLast {
                    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, which is shown with `gradle tasks`.

Rules are not only used when calling tasks from the command line. You can also create dependsOn relations on rule based tasks:

Example 25. Dependency on rule based tasks
build.gradle.kts

    tasks.addRule("Pattern: ping<ID>") {
        val taskName = this
        if (startsWith("ping")) {
            task(taskName) {
                doLast {
                    println("Pinging: " + (taskName.replace("ping", "")))
                }
            }
        }
    }

    tasks.register("groupPing") {
        dependsOn("pingServer1", "pingServer2")
    }

build.gradle

    tasks.addRule("Pattern: ping<ID>") { String taskName ->

        if (taskName.startsWith("ping")) {
            task(taskName) {
                doLast {
                    println "Pinging: " + (taskName - 'ping')
                }
            }
        }
    }

    tasks.register('groupPing') {
        dependsOn 'pingServer1', 'pingServer2'
    }

Output of `gradle -q groupPing`

    > gradle -q groupPing
    Pinging: Server1
    Pinging: Server2

If you run `gradle -q tasks` you won’t find a task named `pingServer1` or `pingServer2`, but this script is executing logic based on the request to run those tasks.

## Finalizer tasks

Finalizer tasks are automatically added to the task graph when the finalized task is scheduled to run.

Example 26. Adding a task finalizer
build.gradle.kts

    val taskX by tasks.registering {
        doLast {
            println("taskX")
        }
    }
    val taskY by tasks.registering {
        doLast {
            println("taskY")
        }
    }

    taskX { finalizedBy(taskY) }

build.gradle

    def taskX = tasks.register('taskX') {
        doLast {
            println 'taskX'
        }
    }
    def taskY = tasks.register('taskY') {
        doLast {
            println 'taskY'
        }
    }

    taskX.configure { finalizedBy taskY }

Output of `gradle -q taskX`

    > gradle -q taskX
    taskX
    taskY

Finalizer tasks will be executed even if the finalized task fails or if the finalized task is considered up to date.

Example 27. Task finalizer for a failing task
build.gradle.kts

    val taskX by tasks.registering {
        doLast {
            println("taskX")
            throw RuntimeException()
        }
    }
    val taskY by tasks.registering {
        doLast {
            println("taskY")
        }
    }

    taskX { finalizedBy(taskY) }

build.gradle

    def taskX = tasks.register('taskX') {
        doLast {
            println 'taskX'
            throw new RuntimeException()
        }
    }
    def taskY = tasks.register('taskY') {
        doLast {
            println 'taskY'
        }
    }

    taskX.configure { finalizedBy taskY }

Output of `gradle -q taskX`

    > gradle -q taskX
    taskX
    taskY

    FAILURE: Build failed with an exception.

    * Where:
    Build file '/home/user/gradle/samples/build.gradle' line: 4

    * What went wrong:
    Execution failed for task ':taskX'.
    > java.lang.RuntimeException (no error message)

    * Try:
    > Run with --stacktrace option to get the stack trace.
    > Run with --info or --debug option to get more log output.
    > Run with --scan to get full insights.
    > Get more help at https://help.gradle.org.

    BUILD FAILED in 0s

Finalizer tasks are useful in situations where the build creates a resource that has to be cleaned up regardless of the build failing or succeeding. An example of such a resource is a web container that is started before an integration test task and which should be always shut down, even if some of the tests fail.

To specify a finalizer task you use the Task.finalizedBy(java.lang.Object…​) method. This method accepts a task instance, a task name, or any other input accepted by Task.dependsOn(java.lang.Object…​).

## Lifecycle tasks

Lifecycle tasks are tasks that do not do work themselves. They typically do not have any task actions. Lifecycle tasks can represent several concepts:

- a work-flow step (e.g., run all checks with `check`)

- a buildable thing (e.g., create a debug 32-bit executable for native components with `debug32MainExecutable`)

- a convenience task to execute many of the same logical tasks (e.g., run all compilation tasks with `compileAll`)

The Base Plugin defines several standard lifecycle tasks, such as `build`, `assemble`, and `check`. All the core language plugins, like the Java Plugin, apply the Base Plugin and hence have the same base set of lifecycle tasks.

Unless a lifecycle task has actions, its outcome is determined by its task dependencies. If any of those dependencies are executed, the lifecycle task will be considered `EXECUTED`. If all of the task dependencies are up to date, skipped or from cache, the lifecycle task will be considered `UP-TO-DATE`.

## Summary

If you are coming from Ant, an enhanced Gradle task like Copy seems like a cross between an Ant target and an Ant task. Although Ant’s tasks and targets are really different entities, Gradle combines these notions into a single entity. Simple Gradle tasks are like Ant’s targets, but enhanced Gradle tasks also include aspects of Ant tasks. All of Gradle’s tasks share a common API and you can create dependencies between them. These tasks are much easier to configure than an Ant task. They make full use of the type system, and are more expressive and easier to maintain.

### Moved documentation

Some documentation previously appearing in this chapter has been moved to the Incremental Build chapter.

#### Up-to-date checks (AKA Incremental Build)

Moved to the Incremental Build chapter.

#### Task inputs and outputs

Moved to a section under Incremental Build.

#### Custom task types

Moved to a section under Incremental Build.

#### Using dependency resolution results

Moved to a section under Incremental Build.

#### Using the classpath annotations

Moved to a section under Incremental Build.

#### Nested inputs

Moved to a section under Incremental Build.

#### Runtime validation

Moved to a section under Incremental Build.

#### Runtime API

Moved to a section under Incremental Build.

#### Using it for ad-hoc tasks

Moved to a section under Incremental Build.

#### Fine-grained configuration

Moved to a section under Incremental Build.

#### Using it for custom task types

Moved to a section under Incremental Build.

#### Important beneficial side effects

Moved to a section under Incremental Build.

#### Inferred task dependencies

Moved to a section under Incremental Build.

#### Input and output validation

Moved to a section under Incremental Build.

#### Continuous build

Moved to a section under Incremental Build.

#### Task parallelism

Moved to a section under Incremental Build.

#### How does it work?

Moved to a section under Incremental Build.

#### Advanced techniques

Moved to a section under Incremental Build.

#### Adding your own cached input/output methods

Moved to a section under Incremental Build.

#### Linking an `@OutputDirectory` to an `@InputFiles`

Moved to a section under Incremental Build.

#### Disabling up-to-date checks

Moved to a section under Incremental Build.

#### Integrate an external tool which does its own up-to-date checking

Moved to a section under Incremental Build.

#### Configure input normalization

Moved to a section under Incremental Build.

#### Properties file normalization

Moved to a section under Incremental Build.

#### Java `META-INF` normalization

Moved to a section under Incremental Build.

#### Providing custom up-to-date logic

Moved to a section under Incremental Build.

#### Stale task outputs

Moved to a section under Incremental Build.

1. You might be wondering why there is neither an import for the `StopExecutionException` nor do we access it via its fully qualified name. The reason is, that Gradle adds a set of default imports to your script (see Default imports).