任務相依性允許任務根據其相依性以特定順序執行。這確保了依賴於其他任務的任務僅在這些相依性完成後才執行。

任務相依性可以分為 **隱含** 或 **明確** 兩種。
- 隱含相依性
-
這些相依性由 Gradle 根據任務的動作和配置自動推斷。例如,如果
taskB
使用taskA
的輸出(例如,由taskA
生成的文件),Gradle 將自動確保在執行taskB
之前先執行taskA
,以滿足此相依性。 - 明確相依性
-
這些相依性在建置腳本中使用
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
之前執行)。 -
在建置早期執行建置驗證(例如,在開始發行版本建置的工作之前,驗證我是否擁有正確的憑證)。
-
通過在長時間驗證任務之前執行快速驗證任務來更快地獲得回饋(例如,單元測試應在整合測試之前執行)。
-
一個彙總特定類型所有任務結果的任務(例如,測試報告任務結合了所有已執行測試任務的輸出)。
有兩個排序規則可用:「*必須在之後執行*」和「*應該在之後執行*」。
若要在 2 個任務之間指定「必須在之後執行」或「應該在之後執行」排序,您可以使用 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
可能會執行。
最終任務
當排定最終任務執行時,最終任務會自動新增到任務圖中。
若要指定最終任務,您可以使用 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)
}
任務規則
有時您想要擁有一個任務,其行為取決於大量或無限數量的參數值範圍。提供此類任務的一種非常出色且富有表現力的方式是任務規則
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
是一種應避免的做法,儘管仍然很常見。