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

任务依赖关系可分为隐式或显式。
- 隐式依赖关系
-
这些依赖关系是 Gradle 根据任务的操作和配置自动推断出来的。例如,如果
taskB
使用taskA
的输出(例如taskA
生成的文件),Gradle 将自动确保taskA
在taskB
之前执行,以满足此依赖关系。 - 显式依赖关系
-
这些依赖关系使用
dependsOn
、mustRunAfter
或shouldRunAfter
方法在构建脚本中显式声明。例如,如果您想确保taskB
始终在taskA
之后运行,可以使用taskB.mustRunAfter(taskA)
显式声明此依赖关系。
隐式和显式依赖关系都在定义任务执行顺序和确保任务按正确序列执行以产生所需构建输出方面发挥着关键作用。
任务依赖关系
Gradle 本身理解任务之间的依赖关系。因此,当您指定某个任务时,它可以确定需要执行哪些任务。
让我们以一个包含 app
子项目和 some-logic
子项目的示例应用程序为例
rootProject.name = "gradle-project"
include("app")
include("some-logic")
rootProject.name = 'gradle-project'
include('app')
include('some-logic')
假设 app
子项目依赖于名为 some-logic
的子项目,该子项目包含一些 Java 代码。我们在 app
构建脚本中添加此依赖关系
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
}
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
有关任务依赖关系的更多信息,请参阅 Task API。
任务排序
在某些情况下,控制两个任务的执行顺序很有用,而无需在这些任务之间引入显式依赖关系。
任务排序和任务依赖关系之间的主要区别在于,排序规则不影响执行哪些任务,只影响它们的执行顺序。
任务排序在多种场景下非常有用
-
强制任务的顺序执行(例如,
build
绝不能在clean
之前运行)。 -
在构建早期运行构建验证(例如,在开始发布构建工作之前验证是否拥有正确的凭据)。
-
通过在长时间验证任务之前运行快速验证任务来更快地获得反馈(例如,单元测试应该在集成测试之前运行)。
-
汇总所有特定类型任务结果的任务(例如,测试报告任务合并所有已执行测试任务的输出)。
提供了两种排序规则:“必须在之后运行”和“应该在之后运行”。
要指定两个任务之间的“必须在之后运行”或“应该在之后运行”排序,您可以使用 Task.mustRunAfter(java.lang.Object...) 和 Task.shouldRunAfter(java.lang.Object...) 方法。这些方法接受任务实例、任务名称或 Task.dependsOn(java.lang.Object...) 接受的任何其他输入。
当您使用“必须在之后运行”时,您指定了当构建需要执行 taskX
和 taskY
时,taskY
必须始终在 taskX
之后运行。因此,如果您只运行带有 mustRunAfter
的 taskY
,它不会导致 taskX
运行。这表示为 taskY.mustRunAfter(taskX)
。
val taskX by tasks.registering {
doLast {
println("taskX")
}
}
val taskY by tasks.registering {
doLast {
println("taskY")
}
}
taskY {
mustRunAfter(taskX)
}
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
“应该在之后运行”排序规则类似但不那么严格,因为它在两种情况下会被忽略
-
如果使用该规则引入了排序循环。
-
在使用并行执行时,除了“应该在之后运行”的任务外,所有任务依赖关系都已满足,那么无论其“应该在之后运行”的依赖关系是否已运行,该任务都会运行。
在排序有帮助但不严格要求的情况下,您应该使用“应该在之后运行”
val taskX by tasks.registering {
doLast {
println("taskX")
}
}
val taskY by tasks.registering {
doLast {
println("taskY")
}
}
taskY {
shouldRunAfter(taskX)
}
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
如果“应该在之后运行”排序规则引入排序循环,它将被忽略
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) }
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)
并不意味着任务之间存在任何执行依赖关系
-
可以独立执行
taskX
和taskY
。排序规则仅在两个任务都被调度执行时才生效。 -
当使用
--continue
运行时,如果taskX
失败,taskY
可能仍然执行。
Finalizer 任务
当 finalized 任务被调度运行时,Finalizer 任务会自动添加到任务图中。
要指定 finalizer 任务,请使用 Task.finalizedBy(java.lang.Object…) 方法。此方法接受任务实例、任务名称或 Task.dependsOn(java.lang.Object…) 接受的任何其他输入
val taskX by tasks.registering {
doLast {
println("taskX")
}
}
val taskY by tasks.registering {
doLast {
println("taskY")
}
}
taskX { finalizedBy(taskY) }
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
即使 finalized 任务失败或被视为 UP-TO-DATE
,Finalizer 任务也会执行。
val taskX by tasks.registering {
doLast {
println("taskX")
throw RuntimeException()
}
}
val taskY by tasks.registering {
doLast {
println("taskY")
}
}
taskX { finalizedBy(taskY) }
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 get full insights. > Get more help at https://help.gradle.org. BUILD FAILED in 0s
Finalizer 任务在构建创建了无论构建失败还是成功都必须清理的资源时非常有用。例如,在集成测试任务之前启动的 Web 容器,即使某些测试失败,也必须将其关闭。
跳过任务
Gradle 提供了多种方法来跳过任务的执行。
1. 使用断言
您可以使用 Task.onlyIf
为任务附加断言。任务的操作只有在该断言评估为 true
时才会执行。
断言作为参数传递给任务,如果任务将执行则返回 true
,如果任务将被跳过则返回 false
。断言在任务执行之前进行评估。
向 onlyIf()
传递一个可选的原因字符串有助于解释任务被跳过的原因
val hello by tasks.registering {
doLast {
println("hello world")
}
}
hello {
val skipProvider = providers.gradleProperty("skipHello")
onlyIf("there is no property skipHello") {
!skipProvider.isPresent()
}
}
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
。
如果操作抛出此异常,则该任务操作以及后续任何操作的执行都将被跳过。构建将继续执行下一个任务
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")
}
}
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
val disableMe by tasks.registering {
doLast {
println("This should not be printed if the task is disabled.")
}
}
disableMe {
enabled = false
}
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
。
Finalizer 任务将执行。如果使用 --continue
,其他任务将继续运行。
不响应中断的任务无法超时。所有 Gradle 的内置任务都响应超时。
tasks.register("hangingTask") {
doLast {
Thread.sleep(100000)
}
timeout = Duration.ofMillis(500)
}
tasks.register("hangingTask") {
doLast {
Thread.sleep(100000)
}
timeout = Duration.ofMillis(500)
}
任务规则
有时您可能希望任务的行为取决于大量或无限范围的参数值。提供此类任务的一种非常棒且富有表现力的方式是任务规则
tasks.addRule("Pattern: ping<ID>") {
val taskName = this
if (startsWith("ping")) {
task(taskName) {
doLast {
println("Pinging: " + (taskName.replace("ping", "")))
}
}
}
}
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
关系
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")
}
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
,您不会找到名为 pingServer1
或 pingServer2
的任务,但该脚本会根据运行这些任务的请求执行逻辑。
从执行中排除任务
您可以使用 -x
或 --exclude-task
命令行选项并提供要排除的任务名称来排除任务的执行。
$ ./gradlew build -x test
例如,您可以运行 check
任务但排除 test
任务的运行。这种方法可能导致意外结果,特别是当您排除一个可执行任务,而该任务产生的结果是其他任务所必需的。建议不要依赖 -x
参数,而是为所需操作定义一个合适的生命周期任务。
使用 -x
是一种应该避免的做法,尽管仍然普遍存在。
StopExecutionException
,也没有通过其完全限定名访问它。原因是 Gradle 会向您的脚本添加一组默认导入(参见 Default imports)。