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

工作相依性可分類為隱含或明確
- 隱含相依性
-
這些相依性會根據工作的動作和設定由 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
,我們會看到 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()` 方法來定義相依性是最簡單的方法。
以下是一個範例,其中新增了從 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...) 接受的任何其他輸入。
當您使用「must run after」時,您指定 taskY
必須在建置需要執行 taskX
和 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
「should run after」排序規則類似,但較不嚴格,因為它會在兩種情況下被忽略
-
如果使用該規則會引入排序循環。
-
當使用平行執行且除了「should run after」任務之外,所有任務相依性都已滿足時,則無論其「should run after」相依性是否已執行,都將執行此任務。
您應該在排序有幫助但不是絕對必要時使用「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
如果「should run after」排序規則會引入排序循環,則會被忽略
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)
}
從執行中排除任務
您可以使用 -x
或 --exclude-task
命令列選項從執行中排除任務,並提供要排除的任務名稱。
$ ./gradlew build -x test
例如,您可以執行 check
任務,但排除執行 test
任務。此方法可能會導致意外的結果,特別是如果您排除會產生其他任務所需結果的可執行任務時。建議您定義適合所需動作的生命週期任務,而不是依賴 -x
參數。
使用 -x
是一種應該避免的作法,儘管仍然很常見。