工作相依性允許工作根據其相依性以特定順序執行。這可確保依賴於其他工作的只在這些相依性完成後才執行。

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,我們會看到 Gradle 也會自動編譯 some-logic 的 Java 程式碼

$./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()` 方法來定義相依性是最簡單的方法。

以下是一個範例,其中新增了從 taskXtaskY 的相依性

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...) 接受的任何其他輸入。

當您使用「must run after」時,您指定 taskY 必須在建置需要執行 taskXtaskY 時,總是執行在 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

「should run after」排序規則類似,但較不嚴格,因為它會在兩種情況下被忽略

  1. 如果使用該規則會引入排序循環。

  2. 當使用平行執行且除了「should run after」任務之外,所有任務相依性都已滿足時,則無論其「should run after」相依性是否已執行,都將執行此任務。

您應該在排序有幫助但不是絕對必要時使用「should run after」

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

在上述範例中,仍然可以執行 taskY 而不會導致 taskX 執行

$ gradle -q taskY
taskY

如果「should run after」排序規則會引入排序循環,則會被忽略

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

從執行中排除任務

您可以使用 -x--exclude-task 命令列選項從執行中排除任務,並提供要排除的任務名稱。

$ ./gradlew build -x test

例如,您可以執行 check 任務,但排除執行 test 任務。此方法可能會導致意外的結果,特別是如果您排除會產生其他任務所需結果的可執行任務時。建議您定義適合所需動作的生命週期任務,而不是依賴 -x 參數。

使用 -x 是一種應該避免的作法,儘管仍然很常見。


1. 您可能想知道為什麼沒有導入 StopExecutionException,也沒有透過其完全限定名稱存取它。原因是 Gradle 會將一組預設導入新增到您的腳本(請參閱 預設導入)。