Avoid DependsOn

The task dependsOn method should only be used for lifecycle tasks (tasks without task actions).

Explanation

Tasks with actions should declare their inputs and outputs so that Gradle’s up-to-date checking can automatically determine when these tasks need to be run or rerun.

Using dependsOn to link tasks is a much coarser-grained mechanism that does not allow Gradle to understand why a task requires a prerequisite task to run, or which specific files from a prerequisite task are needed. dependsOn forces Gradle to assume that every file produced by a prerequisite task is needed by this task. This can lead to unnecessary task execution and decreased build performance.

Example

Here is a task that writes output to two separate files:

build.gradle.kts
abstract class SimplePrintingTask : DefaultTask() {
    @get:OutputFile
    abstract val messageFile: RegularFileProperty

    @get:OutputFile
    abstract val audienceFile: RegularFileProperty

    @TaskAction (1)
    fun run() {
        messageFile.get().asFile.writeText("Hello")
        audienceFile.get().asFile.writeText("World")
    }
}

tasks.register<SimplePrintingTask>("helloWorld") { (2)
    messageFile.set(layout.buildDirectory.file("message.txt"))
    audienceFile.set(layout.buildDirectory.file("audience.txt"))
}
build.gradle
abstract class SimplePrintingTask extends DefaultTask {
    @OutputFile
    abstract RegularFileProperty getMessageFile()

    @OutputFile
    abstract RegularFileProperty getAudienceFile()

    @TaskAction (1)
    void run() {
        messageFile.get().asFile.write("Hello")
        audienceFile.get().asFile.write("World")
    }
}

tasks.register("helloWorld", SimplePrintingTask) { (2)
    messageFile = layout.buildDirectory.file("message.txt")
    audienceFile = layout.buildDirectory.file("audience.txt")
}
1 Task With Multiple Outputs: helloWorld task prints "Hello" to its messageFile and "World" to its audienceFile.
2 Registering the Task: helloWorld produces "message.txt" and "audience.txt" outputs.

Don’t Do This

If you want to translate the greeting in the message.txt file using another task, you could do this:

build.gradle.kts
abstract class SimpleTranslationTask : DefaultTask() {
    @get:InputFile
    abstract val messageFile: RegularFileProperty

    @get:OutputFile
    abstract val translatedFile: RegularFileProperty

    init {
        messageFile.convention(project.layout.buildDirectory.file("message.txt"))
        translatedFile.convention(project.layout.buildDirectory.file("translated.txt"))
    }

    @TaskAction (1)
    fun run() {
        val message = messageFile.get().asFile.readText(Charsets.UTF_8)
        val translatedMessage = if (message == "Hello") "Bonjour" else "Unknown"

        logger.lifecycle("Translation: " + translatedMessage)
        translatedFile.get().asFile.writeText(translatedMessage)
    }
}

tasks.register<SimpleTranslationTask>("translateBad") {
    dependsOn(tasks.named("helloWorld")) (2)
}
build.gradle
abstract class SimpleTranslationTask extends DefaultTask {
    @InputFile
    abstract RegularFileProperty getMessageFile()

    @OutputFile
    abstract RegularFileProperty getTranslatedFile()

    SimpleTranslationTask() {
        messageFile.convention(project.layout.buildDirectory.file("message.txt"))
        translatedFile.convention(project.layout.buildDirectory.file("translated.txt"))
    }

    @TaskAction (1)
    void run() {
        def message = messageFile.get().asFile.text
        def translatedMessage = message == "Hello" ? "Bonjour" : "Unknown"

        logger.lifecycle("Translation: " + translatedMessage)
        translatedFile.get().asFile.write(translatedMessage)
    }
}

tasks.register("translateBad", SimpleTranslationTask) {
    dependsOn(tasks.named("helloWorld")) (2)
}
1 Translation Task Setup: translateBad requires helloWorld to run first to produce the message file otherwise it will fail with an error as the file does not exist.
2 Explicit Task Dependency: Running translateBad will cause helloWorld to run first, but Gradle does not understand why.

Do This Instead

Instead, you should explicitly wire task inputs and outputs like this:

build.gradle.kts
tasks.register<SimpleTranslationTask>("translateGood") {
    inputs.file(tasks.named<SimplePrintingTask>("helloWorld").map { messageFile }) (1)
}
build.gradle
tasks.register("translateGood", SimpleTranslationTask) {
    inputs.file(tasks.named("helloWorld", SimplePrintingTask).map { messageFile }) (1)
}
1 Register Implicit Task Dependency: translateGood requires only one of the files that is produced by helloWorld.

Gradle now understands that translateGood requires helloWorld to have run successfully first because it needs to create the message.txt file which is then used by the translation task. Gradle can use this information to optimize task scheduling. Using the map method avoids eagerly retrieving the helloWorld task until the output is needed to determine if translateGood should run.

Favor @CacheableTask and @DisableCachingByDefault over cacheIf(Spec) and doNotCacheIf(String, Spec)

The cacheIf and doNotCacheIf methods should only be used in situations where the cacheability of a task varies between different task instances or cannot be determined until the task is executed by Gradle. You should instead favor annotating the task class itself with @CacheableTask annotation for any task that is always cacheable. Likewise, the @DisableCachingByDefault should be used to always disable caching for all instances of a task type.

Explanation

Annotating a task type will ensure that each task instance of that type is properly understood by Gradle to be cacheable (or not cacheable). This removes the need to remember to configure each of the task instances separately in build scripts.

Using the annotations also documents the intended cacheability of the task type within its own source, appearing in Javadoc and making the task’s behavior clear to other developers without requiring them to inspect each task instance’s configuration. It is also slightly more efficient than running a test to determine cacheability.

Remember that only tasks that produce reproducible and relocatable output should be marked as @CacheableTask.

Example

Don’t Do This

If you want to reuse the output of a task, you shouldn’t do this:

build.gradle.kts
tasks.register<Delete>("clean") {
    delete(layout.buildDirectory)
}

abstract class BadCalculatorTask : DefaultTask() { (1)
    @get:Input
    abstract val first: Property<Int>

    @get:Input
    abstract val second: Property<Int>

    @get:OutputFile
    abstract val outputFile: RegularFileProperty

    @TaskAction
    fun run() {
        val result = first.get() + second.get()
        logger.lifecycle("Result: $result")
        outputFile.get().asFile.writeText(result.toString())
    }
}

tasks.register<BadCalculatorTask>("addBad1") {
    first = 10
    second = 25
    outputFile = layout.buildDirectory.file("badOutput.txt")
    outputs.cacheIf { true } (2)
}

tasks.register<BadCalculatorTask>("addBad2") { (3)
    first = 3
    second = 7
    outputFile = layout.buildDirectory.file("badOutput2.txt")
}
build.gradle
tasks.register("clean", Delete) {
    delete layout.buildDirectory
}

abstract class BadCalculatorTask extends DefaultTask {
    @Input
    abstract Property<Integer> getFirst()

    @Input
    abstract Property<Integer> getSecond()

    @OutputFile
    abstract RegularFileProperty getOutputFile()

    @TaskAction
    void run() {
        def result = first.get() + second.get()
        logger.lifecycle("Result: " + result)
        outputFile.get().asFile.write(result.toString())
    }
}

tasks.register("addBad1", BadCalculatorTask) {
    first = 10
    second = 25
    outputFile = layout.buildDirectory.file("badOutput.txt")
    outputs.cacheIf { true }
}

tasks.register("addBad2", BadCalculatorTask) {
    first = 3
    second = 7
    outputFile = layout.buildDirectory.file("badOutput2.txt")
}
1 Define a Task: The BadCalculatorTask type is deterministic and produces relocatable output, but is not annotated.
2 Mark the Task Instance as Cacheable: This example shows how to mark a specific task instance as cacheable.
3 Forget to Mark a Task Instance as Cacheable: Unfortunately, the addBad2 instance of the BadCalculatorTask type is not marked as cacheable, so it will not be cached, despite behaving the same as addBad1.

Do This Instead

As this task meets the criteria for cacheability (we can imagine a more complex calculation in the @TaskAction that would benefit from automatic work avoidance via caching), you should mark the task type itself as cacheable like this:

build.gradle.kts
@CacheableTask (1)
abstract class GoodCalculatorTask : DefaultTask() {
    @get:Input
    abstract val first: Property<Int>

    @get:Input
    abstract val second: Property<Int>

    @get:OutputFile
    abstract val outputFile: RegularFileProperty

    @TaskAction
    fun run() {
        val result = first.get() + second.get()
        logger.lifecycle("Result: $result")
        outputFile.get().asFile.writeText(result.toString())
    }
}

tasks.register<GoodCalculatorTask>("addGood1") { (2)
    first = 10
    second = 25
    outputFile = layout.buildDirectory.file("goodOutput.txt")
}

tasks.register<GoodCalculatorTask>("addGood2") {
    first = 3
    second = 7
    outputFile = layout.buildDirectory.file("goodOutput2.txt")
}
build.gradle
@CacheableTask (1)
abstract class GoodCalculatorTask extends DefaultTask {
    @Input
    abstract Property<Integer> getFirst()

    @Input
    abstract Property<Integer> getSecond()

    @OutputFile
    abstract RegularFileProperty getOutputFile()

    @TaskAction
    void run() {
        def result = first.get() + second.get()
        logger.lifecycle("Result: " + result)
        outputFile.get().asFile.write(result.toString())
    }
}

tasks.register("addGood1", GoodCalculatorTask) {
    first = 10
    second = 25
    outputFile = layout.buildDirectory.file("goodOutput.txt")
}

tasks.register("addGood2", GoodCalculatorTask) { (2)
    first = 3
    second = 7
    outputFile = layout.buildDirectory.file("goodOutput2.txt")
}
1 Annotate the Task Type: Applying the @CacheableTask to a task type informs Gradle that instances of this task should always be cached.
2 Nothing Else Needs To Be Done: When we register task instances, nothing else needs to be done - Gradle knows to cache them.