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

writing tasks 2

任務相依性可以分為 **隱含** 或 **明確** 兩種。

隱含相依性

這些相依性由 Gradle 根據任務的動作和配置自動推斷。例如,如果 taskB 使用 taskA 的輸出(例如,由 taskA 生成的文件),Gradle 將自動確保在執行 taskB 之前先執行 taskA,以滿足此相依性。

明確相依性

這些相依性在建置腳本中使用 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

有關任務相依性的更多資訊,請參閱 Task API。

任務排序

在某些情況下,控制兩個任務的執行 *順序* 非常有用,而無需在這兩個任務之間引入明確的相依性。

任務 *排序* 和任務 *相依性* 之間的主要區別在於,排序規則不會影響將執行哪些任務,而只會影響它們的執行順序。

任務排序在許多場景中都很有用

  • 強制任務的循序排序(例如,build 永遠不會在 clean 之前執行)。

  • 在建置早期執行建置驗證(例如,在開始發行版本建置的工作之前,驗證我是否擁有正確的憑證)。

  • 通過在長時間驗證任務之前執行快速驗證任務來更快地獲得回饋(例如,單元測試應在整合測試之前執行)。

  • 一個彙總特定類型所有任務結果的任務(例如,測試報告任務結合了所有已執行測試任務的輸出)。

有兩個排序規則可用:「*必須在之後執行*」和「*應該在之後執行*」。

若要在 2 個任務之間指定「必須在之後執行」或「應該在之後執行」排序,您可以使用 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 get full insights.
> 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 向您的腳本新增了一組預設導入(請參閱預設導入)。