任务依赖关系允许任务根据其依赖关系按特定顺序执行。这确保了依赖于其他任务的任务仅在这些依赖关系完成后才执行。
任务依赖关系可以分为隐式依赖关系和显式依赖关系:
- 隐式依赖
-
这些依赖关系是 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
}
如果我们再次运行,我们会看到Gradle 也自动编译了:app:build
Java 代码:some-logic
$./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
添加依赖项
有多种方法可以定义任务的依赖关系。
使用任务名称和dependentOn()`方法定义依赖关系是最简单的。
以下是添加依赖项 from taskX
to 的示例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...)接受的任何其他输入。
当您使用“must run after”时,您指定必须始终在构建需要执行和taskY
后运行。因此,如果您仅使用 运行,则不会导致运行。这表示为。taskX
taskX
taskY
taskY
mustRunAfter
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
“应该在之后运行”排序规则类似,但不太严格,因为它在两种情况下会被忽略:
-
如果使用该规则会引入订购周期。
-
当使用并行执行并且除了“应该在之后运行”任务之外的所有任务依赖关系都已得到满足时,无论其“应该在之后运行”依赖关系是否已运行,该任务都将运行。
当排序有帮助但不严格要求时,您应该使用“should run after”:
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
在上面的例子中,仍然可以执行taskY
而不导致taskX
运行:
$ 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
,如果失败则可以taskY
执行taskX
。
终结器任务
当计划运行最终任务时,终结器任务会自动添加到任务图中。
要指定终结器任务,请使用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
即使终结任务失败或者考虑终结任务,终结器任务也会执行UP-TO-DATE
:
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
当构建创建必须清理的资源时,无论构建失败还是成功,终结器任务都非常有用。此类资源的一个示例是 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
。
执行终结器任务。如果--continue
使用,其他任务将继续运行。
不响应中断的任务不能超时。 Gradle 的所有内置任务都会响应超时。
tasks.register("hangingTask") {
doLast {
Thread.sleep(100000)
}
timeout = Duration.ofMillis(500)
}
tasks.register("hangingTask") {
doLast {
Thread.sleep(100000)
}
timeout = Duration.ofMillis(500)
}
从执行中排除任务
-x
您可以使用或--exclude-task
命令行选项从执行中排除任务,并提供要排除的任务名称。
$ ./gradlew build -x test
例如,您可以运行该check
任务,但排除该test
任务运行。这种方法可能会导致意想不到的结果,特别是如果您排除了产生其他任务所需结果的可操作任务。-x
建议为所需操作定义合适的生命周期任务,而不是依赖参数。
-x
尽管这种做法仍然很常见,但应该避免使用。