任务依赖允许任务根据其依赖以特定顺序执行。这确保了依赖于其他任务的任务仅在其依赖完成后才执行。

writing tasks 2

任务依赖可以分为隐式显式

隐式依赖

这些依赖由Gradle根据任务的行为和配置自动推断。例如,如果taskB使用taskA的输出(例如,由taskA生成的文件),Gradle将自动确保taskAtaskB之前执行以满足此依赖。

显式依赖

这些依赖在构建脚本中使用dependsOnmustRunAftershouldRunAfter方法明确声明。例如,如果您想确保taskB始终在taskA之后运行,您可以使用taskB.mustRunAfter(taskA)明确声明此依赖。

隐式和显式依赖在定义任务执行顺序和确保任务以正确顺序执行以产生所需构建输出方面都起着至关重要的作用。

任务依赖

Gradle天生就理解任务之间的依赖关系。因此,当您指定一个特定任务时,它能确定需要执行哪些任务。

让我们以一个包含 app 子项目和 some-logic 子项目的示例应用程序为例。

settings.gradle.kts
rootProject.name = "gradle-project"
include("app")
include("some-logic")
settings.gradle
rootProject.name = 'gradle-project'
include('app')
include('some-logic')

假设 app 子项目依赖于名为 some-logic 的子项目,该子项目包含一些 Java 代码。我们在 app 构建脚本中添加此依赖。

app/build.gradle.kts
plugins {
    id("application")                       // app is now a java application
}

application {
    mainClass.set("hello.HelloWorld")       // main class name required by the application plugin
}

dependencies {
    implementation(project(":some-logic"))  // dependency on some-logic
}
app/build.gradle
plugins {
    id('application')                       // app is now a java application
}

application {
    mainClass = 'hello.HelloWorld'          // main class name required by the application plugin
}

dependencies {
    implementation(project(':some-logic'))  // dependency on some-logic
}

如果我们再次运行 :app:build,我们会看到 some-logic 的 Java 代码也会被 Gradle 自动编译。

$./gradlew :app:build

> Task :app:processResources NO-SOURCE
> Task :app:processTestResources NO-SOURCE
> Task :some-logic:compileJava UP-TO-DATE
> Task :some-logic:processResources NO-SOURCE
> Task :some-logic:classes UP-TO-DATE
> Task :some-logic:jar UP-TO-DATE
> Task :app:compileJava
> Task :app:classes
> Task :app:jar UP-TO-DATE
> Task :app:startScripts
> Task :app:distTar
> Task :app:distZip
> Task :app:assemble
> Task :app:compileTestJava UP-TO-DATE
> Task :app:testClasses UP-TO-DATE
> Task :app:test
> Task :app:check
> Task :app:build

BUILD SUCCESSFUL in 430ms
9 actionable tasks: 5 executed, 4 up-to-date

添加依赖项

有几种方法可以定义任务的依赖项。

使用任务名称和 dependsOn() 方法定义依赖项是最简单的。

以下是一个将 taskX 的依赖项添加到 taskY 的示例。

tasks.register("taskX") {
    dependsOn("taskY")
}
tasks.register("taskX") {
    dependsOn "taskY"
}
$ gradle -q taskX
taskY
taskX

有关任务依赖项的更多信息,请参阅 任务 API。

任务排序

在某些情况下,控制两个任务的执行顺序很有用,而无需在这些任务之间引入显式依赖关系。

任务排序和任务依赖的主要区别在于,排序规则不影响哪些任务将被执行,只影响它们将以何种顺序执行。

任务排序在许多场景中都很有用

  • 强制任务的顺序执行(例如,build 绝不能在 clean 之前运行)。

  • 在构建早期运行构建验证(例如,在开始发布构建工作之前验证我拥有正确的凭据)。

  • 通过在长时间验证任务之前运行快速验证任务来更快地获得反馈(例如,单元测试应在集成测试之前运行)。

  • 一个聚合所有特定类型任务结果的任务(例如,测试报告任务结合所有已执行测试任务的输出)。

有两种排序规则可用:“必须在之后运行”和“应该在之后运行”。

要指定两个任务之间的“必须在之后运行”或“应该在之后运行”的顺序,您可以使用 Task.mustRunAfter(java.lang.Object...)Task.shouldRunAfter(java.lang.Object...) 方法。这些方法接受一个任务实例、一个任务名称或 Task.dependsOn(java.lang.Object...) 接受的任何其他输入。

当您使用“必须在之后运行”时,您指定当构建需要执行 taskXtaskY 时,taskY 必须始终在 taskX 之后运行。因此,如果您只使用 mustRunAfter 运行 taskY,您不会导致 taskX 运行。这表示为 taskY.mustRunAfter(taskX)

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
}
$ gradle -q taskY taskX
taskX
taskY

“应该在之后运行”排序规则类似但限制较少,因为它在两种情况下会被忽略:

  1. 如果使用该规则会引入排序循环。

  2. 当使用并行执行且所有任务依赖项都已满足,除了“应该在之后运行”的任务,那么此任务无论其“应该在之后运行”的依赖项是否已运行都将运行。

您应该在排序有用但不严格要求的情况下使用“应该在之后运行”。

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
}
$ gradle -q taskY taskX
taskX
taskY

在上面的示例中,仍然可以在不导致 taskX 运行的情况下执行 taskY

$ gradle -q taskY
taskY

如果“应该在之后运行”排序规则引入排序循环,它将被忽略。

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) }
$ gradle -q taskX
taskZ
taskY
taskX

请注意,taskY.mustRunAfter(taskX)taskY.shouldRunAfter(taskX) 并不意味着任务之间存在任何执行依赖关系。

  • 可以独立执行 taskXtaskY。排序规则仅在两个任务都计划执行时才有效。

  • 当使用 --continue 运行时,如果 taskX 失败,taskY 仍然有可能执行。

终结器任务

当被终结的任务计划运行时,终结器任务会自动添加到任务图中。

要指定一个终结器任务,您可以使用 Task.finalizedBy(java.lang.Object…​) 方法。此方法接受一个任务实例、一个任务名称或 Task.dependsOn(java.lang.Object…​) 接受的任何其他输入。

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 }
$ gradle -q taskX
taskX
taskY

即使被终结的任务失败或被认为 UP-TO-DATE,终结器任务也会执行。

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 }
$ 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 generate a Build Scan (Powered by Develocity).
> Get more help at https://help.gradle.org.

BUILD FAILED in 0s

当构建创建了一个必须清理的资源时,无论构建失败还是成功,终结器任务都很有用。这种资源的一个例子是在集成测试任务之前启动的 Web 容器,即使某些测试失败也必须关闭。

跳过任务

Gradle 提供了多种跳过任务执行的方法。

1. 使用谓词

您可以使用 Task.onlyIf 为任务附加一个谓词。任务的操作只有在谓词评估为 true 时才会被执行。

谓词作为参数传递给任务,如果任务将执行则返回 true,如果任务将被跳过则返回 false。谓词在任务执行前进行评估。

将一个可选的理由字符串传递给 onlyIf() 有助于解释任务被跳过的原因。

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
    }
}
$ gradle hello -PskipHello
> Task :hello SKIPPED

BUILD SUCCESSFUL in 0s

要查找任务被跳过的原因,请使用 --info 日志级别运行构建。

$ 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

2. 使用 StopExecutionException

如果跳过任务的逻辑无法用谓词表达,您可以使用 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'
    }
}
$ gradle -q myTask
I am not affected

如果您使用Gradle提供的任务,此功能会很有帮助。它允许您添加此类任务内置动作的条件执行。[1]

3. 启用和禁用任务

每个任务都有一个 enabled 标志,默认为 true。将其设置为 false 会阻止执行任务的操作。

一个被禁用的任务将被标记为 SKIPPED

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
}
$ gradle disableMe
> Task :disableMe SKIPPED

BUILD SUCCESSFUL in 0s

4. 任务超时

每个任务都有一个 timeout 属性,可用于限制其执行时间。当任务达到其超时时,其任务执行线程将被中断。任务将被标记为 FAILED

终结器任务 将被执行。如果使用 --continue,其他任务将继续运行。

不响应中断的任务无法超时。Gradle 的所有内置任务都响应超时。

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

任务规则

有时您希望任务的行为取决于大量或无限范围的参数值。提供此类任务的一种非常优雅和富有表现力的方式是任务规则。

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')
            }
        }
    }
}
$ gradle -q pingServer1
Pinging: Server1

String 参数用作规则的描述,该描述会显示在 ./gradlew tasks 中。

规则不仅用于从命令行调用任务。您还可以在基于规则的任务上创建 dependsOn 关系。

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'
}
$ gradle -q groupPing
Pinging: Server1
Pinging: Server2

如果您运行 ./gradlew -q tasks,您不会找到名为 pingServer1pingServer2 的任务,但此脚本正在根据运行这些任务的请求执行逻辑。

将任务从执行中排除

您可以使用 -x--exclude-task 命令行选项并提供任务名称来将其从执行中排除。

$ ./gradlew build -x test

例如,您可以运行 check 任务,但排除 test 任务的运行。这种方法可能导致意外结果,特别是如果您排除了一个可执行任务,而该任务产生的某些结果是其他任务所必需的。因此,建议定义一个适合所需操作的生命周期任务,而不是依赖 -x 参数。

使用 -x 是一种应避免的做法,尽管仍然普遍存在。


1. 您可能想知道为什么既没有导入 StopExecutionException,也没有通过其完全限定名访问它。原因是 Gradle 会向您的脚本添加一组默认导入(参见 默认导入)。