1. 程式人生 > >Gradle學習(十四)——任務詳解

Gradle學習(十四)——任務詳解

之前的文章已經將了一下任務的基礎知識,包括建立簡單的任務,以及任務的依賴,本文將進一步的講解一些任務的詳細知識

任務結果

當Gradle執行任務時,就會因為不同的結果給這些任務打上不同的標籤,這些標籤表示任務是否有動作需要執行,任務是否應該執行了動作,任務是否執行了這些動作,這些動作是否發生了變化。

結果標籤 結果描述 產生結果的前提
沒有標籤或者EXECUTED 任務執行了動作 1.任務有動作,Gradle也確定該動作必須被執行
2.任務沒有動作但是有依賴,這些依賴都要被執行
UP-TO-DATE 任務輸出沒有改變 1.當任務有輸入輸出但是沒有變化
2當任務有action,但是任務告訴Gradle輸出沒有變化
3.當任務沒有動作,而是有依賴,但是所有的依賴都是UP-TO-DATE,SKIP或者從快取取出的
4.任務沒有動作也沒有依賴
FROM-CACHE 任務輸出可以在之前的執行快取中被找到 任務在構建緩衝中可以找到相應的輸出
SKIPPED 任務沒有執行動作 1.在命令列顯示指定了被跳過
2.任務的onlyIf判斷返回false
NO-SOURCE 任務不需要執行動作 任務有輸入輸出,但是沒有sources,比如JavaCompile任務的source就是.java檔案

定義任務

之前的章節我們已經學會如何使用keyword方式定義任務,還有一些別的方式來定義任務,因為keyword方式不支援表示式。

task(hello) {
    doLast {
        println 'hello'
} } task(copy, type: Copy) { from file('src') into buildDir }

你也可以通過string來定義任務

task('hello') {
    doLast {
        println 'hello'
    }
}

task('copy', type: Copy) {
    from file('src')
    into buildDir
}

還可以通過tasks來建立任務

tasks.create('hello') {
    doLast {
        println 'hello'
} } tasks.create(name: 'copy', type: Copy) { from file('src') into buildDir }

檢索任務

你有時候需要檢索在構建檔案中定義的任務,比如對任務進行配置或者新增依賴,有很多種方式可以檢索。

首先,所有的任務對project物件來說就是可用屬性,你可以像使用屬性一樣來檢索這些任務。

task hello
println hello.name
println project.hello.name

也可以通過tasks集合來檢索

task hello
println tasks.hello.name
println tasks['hello'].name

也可以通過tasks.getByPath()來訪問任意工程的任務,可以使用任務名,相對路徑,絕對路徑來呼叫getByPath()方法。

task hello
project(':projectA') {
    task hello
}
println tasks.getByPath('hello').path
println tasks.getByPath(':hello').path
println tasks.getByPath('projectA:hello').path
println tasks.getByPath(':projectA:hello').path

然後執行hello任務:

:hello
:hello
:projectA:hello
:projectA:hello

配置任務

我們從一個copy任務的示例來看如何配置Gradle的任務

task myCopy(type:Copy)

這是一個沒有任何預設行為的copy任務,我們有各種各樣的方法可以配置它。

myCopy.from 'resource'
myCopy.into 'build'
myCopy.include '**/*.txt', '**/*.xml', '**/*.properties'

首先就是最普通的方式,像配置java或者groovy物件那樣呼叫方法進行配置,myCopy需要重複好多次,可讀性並不好。
還有一種方法可以不用重複myCopy,看起來可讀性更好些:

myCopy {
    from 'resource'
    into 'build'
    include '**/*.txt', '**/*.xml', '**/*.properties'
}

這種方式其實是呼叫了tasks.getByName(String var1, Closure var2)方法,你看到的是它的縮寫,這個方法完成了使用Closure對任務進行了配置。

你也可以在定義的時候就進行配置:

task myCopy(type:Copy) {
    from 'resource'
    into 'build'
    include '**/*.txt', '**/*.xml', '**/*.properties'
}

給任務新增依賴

有各種各樣的方式可以給任務新增依賴。常用的是通過任務名進行新增依賴,不僅可以在相同專案中可以通過任務名新增依賴,也可以新增其他專案的任務作為依賴。新增其他專案的任務作為依賴時,必須要把其他工程的專案名作為字首,下面是個跨專案依賴的例子:

project(':projectA') {
    task taskX(dependsOn:':projectB:taskY') {
        doLast {
            println it.name
        }
    }
}

project(':projectB') {
    task taskY {
        doLast {
            println it.name
        }
    }
}

然後執行taskX任務:

% gradle taskX -q                                                       
taskY
taskX

除了使用任務名外,你還可以通過Task物件進行新增依賴:

task taskX {
    doLast {
        println it.name
    }
}

task taskY {
    doLast {
        println it.name
    }
}
taskX.dependsOn taskY

然後執行taskX任務:

% gradle taskX -q                                                       
taskY
taskX

再擴充套件一下,你甚至可以定義閉包作為依賴,這個閉包應該返回一個task或者多個task列表,這些task會被當做任務的依賴,示例如下:

task taskX {
    doLast {
        println it.name
    }
}

taskX.dependsOn {
    tasks.findAll {
        it.name.startsWith('lib')
    }
}

task lib1 {
    doLast {
        println it.name
    }
}

task lib2 {
    doLast {
        println it.name
    }
}

然後執行taskX任務:

± % gradle taskX -q   
lib1
lib2
taskX

任務排序

控制兩個任務的執行順序而不是顯示的宣告他們之間的依賴關係,在某些用例下它是非常有用的。任務排序和任務依賴的不同在於,任務排序不會影響哪個任務將被執行,僅僅只是影響將被任務的排序。

任務排序適用的場景:

  • 強制任務的執行順序:比如build必須在clean任務後面執行
  • 在build之後才執行校驗:比如需要在release構建之後才進行認證校驗
  • 在長校驗之前先進行短校驗:比如先單元測試再整合測試
  • 一個任務是其他所有任務的總結:比如測試報告任務需要其他測試任務都做完的情況下

有兩個排序規則:must run aftershould run after

當你執行must run after規則時,taskB.mustRunAfter(taskA)表示taskB和taskA同事執行時,taskB必然在taskA之後執行。

should run after就沒那麼嚴格了,它在兩種情況下就會被忽略掉:
第一種情況就是任務排序構成了迴圈,比如taskB.shouldRunAfter taskA之後又taskA.shouldRunAfter taskB
第二種情況宣告的依賴和should run after的順序不一樣,should run after就會被忽略掉
使用should run after應該在有更好,沒有也行的情況下才使用。
看下面的例子:

task taskA {
    doLast {
        println it.name
    }
}

task taskB {
    doLast {
        println it.name
    }
}
taskB.mustRunAfter taskA

然後執行taskA和taskB任務:

± % gradle taskB taskA -q                                                 
taskA
taskB

should run after的情況:

task taskA {
    doLast {
        println it.name
    }
}

task taskB {
    doLast {
        println it.name
    }
}
taskB.shouldRunAfter taskA

然後執行taskA和taskB任務:

± % gradle taskB taskA -q                                                 
taskA
taskB

上面的例子中,如果單獨執行taskB任務:

± % gradle taskB  -q
taskB

可以看到taskA任務並沒有執行

如果有--continue選項,如果A失敗B也會繼續執行。

task taskA {
  doLast {
      throw new Exception()
      println it.name
  }
}

task taskB {
    doLast {
        println it.name
    }
}
taskB.shouldRunAfter taskA

然後執行taskA和taskB任務:

± % gradle --continue -q taskA taskB                                      
taskB

FAILURE: Build failed with an exception
···

和依賴關係衝突的情況下,shouldRunAfter會被忽略

task taskA {
    doLast {
        println it.name
    }
}

task taskB {
    doLast {
        println it.name
    }
}
taskA.dependsOn taskB
taskB.shouldRunAfter taskA

然後執行

± % gradle -q taskA taskB                                                 
taskB
taskA

如果是mustRunAfter和依賴衝突,那麼就會直接報錯

任務描述

你可以為任務增加描述資訊,他會在gradle tasks顯示出來。

task myCopy(type: Copy) {
    description 'Copy the resource directory to the build directory'
    from 'resource'
    into 'build'
    include '**/*.txt', '**/*.xml', '**/*.properties'
}

替代任務

有時候你需要替代任務,必須你想自定義java外掛的某個任務,可以參照下面這個例子:

task copy(type:Copy){
    doLast {
        println 'copy1'
    }
}
task copy(overwrite: true) {
    doLast {
        println 'copy2'
    }
}

任務輸出:

± % gradle copy -q                                                       
copy2

如果直接定義名字相同的任務,Gradle會丟擲任務已經被定義的異常,因此你需要增加overwrite:true的設定。

跳過任務

Gradle提供了多種方法可以跳過任務的執行

使用predicate

你可以用onlyIf()方法給任務附加一個predicate,只有當predicate為true時,任務的動作才會執行.predicate可以用closure去實現,closure將會作為一個引數傳入到OnlyIf方法中,只有當閉包返回true,任務的action才會執行,否則將會跳過,示例如下:

task hello {
    doLast {
        println 'hello world'
    }
}
hello.onlyIf {
    !project.hasProperty('skipHello')
}

然後執行任務:

± % gradle hello -PskipHello -q

任務跳過,沒有任何輸出

使用StopExecutionException

如果跳過的邏輯很難用predicate協程表示式,那麼可以考慮下StopExecutionException,這個異常可以通過一個action丟擲來,然後該任務之後的action都會跳過,但是會執行下一個任務:

task compile {
    doLast {
        println 'compile'
    }
}
compile.doFirst {
    if(true){
        throw new StopExecutionException()
    }
}
task myTask(dependsOn: compile) {
    doLast {
        println 'myTask'
    }
}

然後執行任務:

± % gradle myTask -q                                                     
myTask

這個特性非常有用,因為它的力度是基於action的,靈活使用可以寫出很多有趣的程式碼

啟用禁用任務

每個任務的enabled標籤預設都是true,如果設為false,它所以的action都不會被執行,任務結果也會變成SKIPPED

task disabledMe {
    doLast {
        println '反正寫什麼也不會被執行'
    }
}
disabledMe.enabled = false

然後執行任務:

± % gradle disabledMe -q

任務規則

有時候你需要任務可以包含無窮的引數,而你不想為每個都單獨寫一個task的話,你可以試試任務規則:

tasks.addRule('Pattern:ping<ID>') { String taskName ->
    if (taskName.startsWith('ping')) {
        task(taskName) {
            doLast {
                println "Panding: " + (taskName - 'ping')
            }
        }
    }
}

你可以這樣執行任務:

± % gradle pingServer1 -q                                                 
Panding: Server1

addRule的第一個引數是描述引數,用於gradle tasks的顯示,在命令列下可以看到,目前idea的gradle外掛還不支援。

任務規則不僅僅可以在命令列下呼叫,而且還可以與其他規則建立以來關係

tasks.addRule('Pattern:ping<ID>') { String taskName ->
    if (taskName.startsWith('ping')) {
        task(taskName) {
            doLast {
                println "Panding: " + (taskName - 'ping')
            }
        }
    }
}

task groupPing {
    dependsOn pingServer1,pingServer2
}

然後執行任務:

± % gradle groupPing -q                                                   
Panding: Server1
Panding: Server2

Finalizer任務

被終結的任務執行後,Finalizer任務必然會跟著執行

task taskC {
    doLast {
        println it.name
    }
}

task taskD {
    doLast {
        println it.name
    }
}
taskC.finalizedBy taskD

執行任務:

± % gradle taskC -q                                                       !1
taskC
taskD

即使被終結的任務失敗了,Finalizer任務一樣會執行

task taskC {
    doLast {
        println it.name
        throw new RuntimeException()
    }
}

task taskD {
    doLast {
        println it.name
    }
}
taskC.finalizedBy taskD

執行任務:

± % gradle taskC -q                                                       
taskC
taskD

FAILURE: Build failed with an exception.
...

僅僅有一種情況下Finalizer任務不會被執行,就是被終極的任務根本沒有工作,比如up to date或者依賴失敗了。

Finalizer任務在無論構建是否成功都必須要清除一些資源的情況下是非常有用的,比如測試中需要的web容器,它在整合測試前需要被開啟,而無論測試是否成功,都需要被關閉。

生命週期任務

生命週期任務本身自己沒有action可以執行,它有一下幾個特點

  • 一個工作流(比如用check執行所以的check任務)
  • 一個可以構建是事物(比如執行debug32MainExecutable為native元件生產32位下的debug可執行程式)
  • 一個可以執行相同邏輯任務的任務(比如compileAll執行所以的編譯任務)

很多Gradel外掛都有自己的生命週期任務,可以方便的指定具體的操作。如果你自己開發Gradle外掛時,一定要記得把相同邏輯的任務作出生命週期任務,以便於操作。

除非生命週期任務有自己的action,否則他的執行與否要看他所有的依賴是否需要執行,如果有一個需要執行,那麼生命週期任務就會被執行。如果所有的依賴都是up-to-date,skipped或者從構建快取拉取的,那麼這個任務就是up-to-date的。