避免使用 DependsOn

任务的 dependsOn 方法应仅用于生命周期任务(不带任务动作的任务)。

解释

带有动作的任务应声明其输入和输出,以便 Gradle 的最新检查可以自动确定何时需要运行或重新运行这些任务。

使用 dependsOn 链接任务是一种更粗粒度的机制,它**不允许** Gradle 理解任务为何需要先决任务运行,或者需要先决任务中的哪些特定文件。dependsOn 强制 Gradle 假设先决任务产生**所有**文件都为此任务所需。这可能导致不必要的任务执行并降低构建性能。

示例

这是一个将输出写入两个单独文件的任务

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 具有多个输出的任务helloWorld 任务将其“Hello”打印到其 messageFile,将其“World”打印到其 audienceFile
2 注册任务helloWorld 产生“message.txt”和“audience.txt”输出。

不要这样做

如果你想使用另一个任务翻译 message.txt 文件中的问候语,你可以这样做

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 翻译任务设置translateBad 需要 helloWorld 首先运行以生成消息文件,否则如果文件不存在,它将失败并报错。
2 显式任务依赖:运行 translateBad 将导致 helloWorld 首先运行,但 Gradle 不理解**原因**。

取而代之这样做

相反,您应该像这样明确地连接任务输入和输出

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>("translateGood") {
    inputs.file(tasks.named<SimplePrintingTask>("helloWorld").map { messageFile }) (1)
}
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("translateGood", SimpleTranslationTask) {
    inputs.file(tasks.named("helloWorld", SimplePrintingTask).map { messageFile }) (1)
}
1 注册隐式任务依赖translateGood 只需要 helloWorld 生成的一个文件。

Gradle 现在明白 translateGood 需要 helloWorld 首先成功运行,因为它需要创建 message.txt 文件,然后该文件由翻译任务使用。Gradle 可以使用此信息来优化任务调度。使用 map 方法可避免在需要输出以确定 translateGood 是否应运行之前急切地检索 helloWorld 任务。

优先使用 @CacheableTask@DisableCachingByDefault,而不是 cacheIf(Spec)doNotCacheIf(String, Spec)

cacheIfdoNotCacheIf 方法应仅在任务的可缓存性在不同任务实例之间变化或在 Gradle 执行任务之前无法确定时使用。您应该优先使用 @CacheableTask 注解来注释*始终*可缓存的任务类本身。同样,@DisableCachingByDefault 应该用于始终禁用所有任务类型实例的缓存。

解释

注释任务类型将确保 Gradle 正确地将该类型的*每个任务实例*理解为可缓存(或不可缓存)。这消除了在构建脚本中单独配置每个任务实例的需要。

使用注解还可以*记录*任务类型在其自身源代码中的预期可缓存性,出现在 Javadoc 中,并使任务的行为对其他开发人员清晰明了,而无需他们检查每个任务实例的配置。它也比运行测试来确定可缓存性稍微高效一些。

请记住,只有产生可重现和可重新定位输出的任务才应标记为 @CacheableTask

示例

不要这样做

如果你想重用任务的输出,你不应该这样做

build.gradle.kts
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<Delete>("clean") {
    delete(layout.buildDirectory)
}

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
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("clean", Delete) {
    delete layout.buildDirectory
}

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 定义任务BadCalculatorTask 类型是确定性的,并生成可重新定位的输出,但未注释。
2 将任务实例标记为可缓存:此示例展示了如何将特定任务实例标记为可缓存。
3 忘记将任务实例标记为可缓存:不幸的是,BadCalculatorTask 类型的 addBad2 实例未标记为可缓存,因此尽管其行为与 addBad1 相同,但它不会被缓存。

取而代之这样做

由于此任务符合可缓存性标准(我们可以想象 @TaskAction 中更复杂的计算将受益于通过缓存自动避免工作),您应该将*任务类型本身*标记为可缓存,如下所示

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<Delete>("clean") {
    delete(layout.buildDirectory)
}

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("clean", Delete) {
    delete layout.buildDirectory
}

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 注释任务类型:将 @CacheableTask 应用于任务类型会告知 Gradle,此任务的实例应*始终*被缓存。
2 无需做其他事情:当我们注册任务实例时,无需做其他事情 - Gradle 知道要缓存它们。

分组和描述自定义任务

在定义自定义任务类型或注册临时任务时,始终设置清晰的 groupdescription

解释

好的组名应该简短、小写,并反映任务的目的或领域。例如:documentationverificationreleasepublishing

在创建新组之前,请查找与任务意图一致的现有组名。通常,重用已建立的类别可以使任务输出井井有条且用户熟悉。

此信息用于任务报告(通过 ./gradlew tasks 显示),以可读格式对可用任务进行分组和描述。

提供组和描述可确保您的任务

  • 在报告中清晰显示

  • 适当分类

  • 对其他用户(以及您未来的自己)可理解

未分组的任务将从任务报告中隐藏,除非指定 --all

示例

不要这样做

没有组的任务会出现在 ./gradlew tasks --all 输出的“其他”类别下,使其更难找到

app/build.gradle.kts
tasks.register("generateDocs") {
    // Build logic to generate documentation
}
app/build.gradle
tasks.register('generateDocs') {
    // Build logic to generate documentation
}
$ gradlew :app:tasks --all

> Task :app:tasks

------------------------------------------------------------
Tasks runnable from project ':app'
------------------------------------------------------------

Other tasks
-----------
compileJava - Compiles main Java source.
compileTestJava - Compiles test Java source.
generateDocs
processResources - Processes main resources.
processTestResources - Processes test resources.
startScripts - Creates OS specific scripts to run the project as a JVM application.

这样做

在定义自定义任务时,始终指定清晰的 groupdescription

app/build.gradle.kts
tasks.register("generateDocs") {
    group = "documentation"
    description = "Generates project documentation from source files."
    // Build logic to generate documentation
}
app/build.gradle
tasks.register('generateDocs') {
    group = 'documentation'
    description = 'Generates project documentation from source files.'
    // Build logic to generate documentation
}
$ gradlew :app:tasks --all

> Task :app:tasks

------------------------------------------------------------
Tasks runnable from project ':app'
------------------------------------------------------------

Documentation tasks
-------------------
generateDocs - Generates project documentation from source files.
javadoc - Generates Javadoc API documentation for the 'main' feature.