任务最佳实践
避免使用 DependsOn
解释
带有动作的任务应声明其输入和输出,以便 Gradle 的最新检查可以自动确定何时需要运行或重新运行这些任务。
使用 dependsOn
链接任务是一种更粗粒度的机制,它**不允许** Gradle 理解任务为何需要先决任务运行,或者需要先决任务中的哪些特定文件。dependsOn
强制 Gradle 假设先决任务产生**所有**文件都为此任务所需。这可能导致不必要的任务执行并降低构建性能。
示例
这是一个将输出写入两个单独文件的任务
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"))
}
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
文件中的问候语,你可以这样做
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)
}
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 不理解**原因**。 |
取而代之这样做
相反,您应该像这样明确地连接任务输入和输出
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)
}
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)
cacheIf
和 doNotCacheIf
方法应仅在任务的可缓存性在不同任务实例之间变化或在 Gradle 执行任务之前无法确定时使用。您应该优先使用 @CacheableTask
注解来注释*始终*可缓存的任务类本身。同样,@DisableCachingByDefault
应该用于始终禁用所有任务类型实例的缓存。
解释
注释任务类型将确保 Gradle 正确地将该类型的*每个任务实例*理解为可缓存(或不可缓存)。这消除了在构建脚本中单独配置每个任务实例的需要。
使用注解还可以*记录*任务类型在其自身源代码中的预期可缓存性,出现在 Javadoc 中,并使任务的行为对其他开发人员清晰明了,而无需他们检查每个任务实例的配置。它也比运行测试来确定可缓存性稍微高效一些。
请记住,只有产生可重现和可重新定位输出的任务才应标记为 @CacheableTask
。
示例
不要这样做
如果你想重用任务的输出,你不应该这样做
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")
}
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
中更复杂的计算将受益于通过缓存自动避免工作),您应该将*任务类型本身*标记为可缓存,如下所示
@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")
}
@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 知道要缓存它们。 |
分组和描述自定义任务
在定义自定义任务类型或注册临时任务时,始终设置清晰的 group
和 description
。
解释
好的组名应该简短、小写,并反映任务的目的或领域。例如:documentation
、verification
、release
或 publishing
。
在创建新组之前,请查找与任务意图一致的现有组名。通常,重用已建立的类别可以使任务输出井井有条且用户熟悉。
此信息用于任务报告(通过 ./gradlew tasks
显示),以可读格式对可用任务进行分组和描述。
提供组和描述可确保您的任务
-
在报告中清晰显示
-
适当分类
-
对其他用户(以及您未来的自己)可理解
未分组的任务将从任务报告中隐藏,除非指定 --all 。 |
示例
不要这样做
没有组的任务会出现在 ./gradlew tasks --all
输出的“其他”类别下,使其更难找到
tasks.register("generateDocs") {
// Build logic to generate documentation
}
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.
这样做
在定义自定义任务时,始终指定清晰的 group
和 description
tasks.register("generateDocs") {
group = "documentation"
description = "Generates project documentation from source files."
// Build logic to generate documentation
}
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.