深入理解gradle中的task
簡介
在之前的文章中,我們講到了如何使用gradle建立一個簡單的task,以及task之間怎麼依賴,甚至使用了程式來建立task。在本文中,我們會更加深入的去了解一下gradle中的task。
定義task
定義一個task可以有很多種方式,比如下面的使用string作為task的名字:
task('hello') {
doLast {
println "hello"
}
}
task('copy', type: Copy) {
from(file('srcDir'))
into(buildDir)
}
還可以使用tasks容器來建立:
tasks.create('hello') {
doLast {
println "hello"
}
}
tasks.create('copy', Copy) {
from(file('srcDir'))
into(buildDir)
}
上面的例子中,我們使用tasks.create方法,將新建立的task加到tasks集合中。
我們還可以使用groovy特有的語法來定義一個task:
task(hello) {
doLast {
println "hello"
}
}
task(copy, type: Copy) {
from(file('srcDir'))
into(buildDir)
}
tasks 集合類
上面我們在建立task的時候,使用了tasks集合類來建立task。
實際上,tasks集合類是一個非常有用的工具類,我們可以使用它來做很多事情。
直接在build檔案中使用tasks,實際上是引用了TaskContainer的一個例項物件。我們還可以使用 Project.getTasks()
來獲取這個例項物件。
我們看下TaskContainer的定義:
public interface TaskContainer extends TaskCollection<Task>, PolymorphicDomainObjectContainer<Task>
從定義上,我們可以看出TaskContainer是一個task的集合和域物件的集合。
taskContainer中有四類非常重要的方法:
第一類是定位task的方法,有個分別是findByPath和getByPath。兩個方法的區別就是findByPath如果沒找到會返回null,而getByPath沒找到的話會丟擲UnknownTaskException。
看下怎麼使用:
task hello
println tasks.getByPath('hello').path
println tasks.getByPath(':hello').path
輸出:
:hello
:hello
第二類是建立task的方法create,create方法有多種實現,你可以直接通過名字來建立一個task:
task('hello') {
doLast {
println "hello"
}
}
也可以建立特定型別的task:
task('copy', type: Copy) {
from(file('srcDir'))
into(buildDir)
}
還可以建立帶引數的建構函式的task:
class CustomTask extends DefaultTask {
final String message
final int number
@Inject
CustomTask(String message, int number) {
this.message = message
this.number = number
}
}
上面我們為CustomTask建立了一個帶引數的建構函式,注意,這裡需要帶上@javax.inject.Inject註解,表示我們後面可以傳遞引數給這個建構函式。
我們可以這樣使用:
tasks.create('myTask', CustomTask, 'hello', 42)
也可以這樣使用:
task myTask(type: CustomTask, constructorArgs: ['hello', 42])
第三類是register,register也是用來建立新的task的,不過register執行的是延遲建立。也就是說只有當task被需要使用的時候才會被建立。
我們先看一個register方法的定義:
TaskProvider<Task> register(String name,
Action<? super Task> configurationAction)
throws InvalidUserDataException
可以看到register返回了一個TaskProvider,有點像java多執行緒中的callable,當我們呼叫Provider.get()獲取task值的時候,才會去建立這個task。
或者我們呼叫TaskCollection.getByName(java.lang.String)的時候也會建立對應的task。
最後一類是replace方法:
Task replace(String name)
<T extends Task> T replace(String name,
Class<T> type)
replace的作用就是建立一個新的task,並且替換掉同樣名字的老的task。
Task 之間的依賴
task之間的依賴關係是通過task name來決定的。我們可以在同一個專案中做task之間的依賴:
task hello {
doLast {
println 'Hello www.flydean.com!'
}
}
task intro {
dependsOn hello
doLast {
println "I'm flydean"
}
}
也可以跨專案進行task的依賴,如果是跨專案的task依賴的話,需要制定task的路徑:
project('project-a') {
task taskX {
dependsOn ':project-b:taskY'
doLast {
println 'taskX'
}
}
}
project('project-b') {
task taskY {
doLast {
println 'taskY'
}
}
}
或者我們可以在定義好task之後,再處理task之間的依賴關係:
task taskX {
doLast {
println 'taskX'
}
}
task taskY {
doLast {
println 'taskY'
}
}
還可以動態新增依賴關係:
task taskX {
doLast {
println 'taskX'
}
}
// Using a Groovy Closure
taskX.dependsOn {
tasks.findAll { task -> task.name.startsWith('lib') }
}
task lib1 {
doLast {
println 'lib1'
}
}
task lib2 {
doLast {
println 'lib2'
}
}
task notALib {
doLast {
println 'notALib'
}
}
定義task之間的順序
有時候我們的task之間是有執行順序的,我們稱之為對task的排序ordering。
先看一下ordering和dependency有什麼區別。dependency表示的是一種強依賴關係,如果taskA依賴於taskB,那麼執行taskA的時候一定要先執行taskB。
而ordering則是一種並不太強列的順序關係。表示taskA需要在taskB之後執行,但是taskB不執行也可以。
在gradle中有兩種order:分別是must run after和should run after。
taskA.mustRunAfter(taskB)表示必須遵守的順序關係,而taskA.shouldRunAfter(taskB)則不是必須的,在下面兩種情況下可以忽略這樣的順序關係:
第一種情況是如果shouldRunAfter引入了order迴圈的時候。
第二種情況是如果在並行執行的情況下,task所有的依賴關係都已經滿足了,那麼也會忽略這個順序。
我們看下怎麼使用:
task taskX {
doLast {
println 'flydean.com'
}
}
task taskY {
doLast {
println 'hello'
}
}
taskY.mustRunAfter taskX
//taskY.shouldRunAfter taskX
給task一些描述
我們可以給task一些描述資訊,這樣我們在執行gradle tasks的時候,就可以檢視到:
task copy(type: Copy) {
description 'Copies the resource directory to the target directory.'
from 'resources'
into 'target'
include('**/*.txt', '**/*.xml', '**/*.properties')
}
task的條件執行
有時候我們需要根據build檔案中的某些屬性來判斷是否執行特定的task,我們可以使用onlyIf :
task hello {
doLast {
println 'www.flydean.com'
}
}
hello.onlyIf { !project.hasProperty('skipHello') }
或者我們可以丟擲StopExecutionException異常,如果遇到這個異常,那麼task後面的任務將不會被執行:
task compile {
doLast {
println 'We are doing the compile.'
}
}
compile.doFirst {
if (true) { throw new StopExecutionException() }
}
task myTask {
dependsOn('compile')
doLast {
println 'I am not affected'
}
}
我們還可以啟動和禁用task:
myTask.enabled = false
最後我們還可以讓task超時,當超時的時候,執行task的執行緒將會被中斷,並且task將會被標記為failed。
如果我們想繼續執行,那麼可以使用 --continue。
注意, 只有能夠響應中斷的task,timeout才有用。
task hangingTask() {
doLast {
Thread.sleep(100000)
}
timeout = Duration.ofMillis(500)
}
task rule
如果我們想要給某些task定義一些規則,那麼可以使用tasks.addRule:
tasks.addRule("Pattern: ping<ID>") { String taskName ->
if (taskName.startsWith("ping")) {
task(taskName) {
doLast {
println "Pinging: " + (taskName - 'ping')
}
}
}
}
上我們定義了一個rule,如果taskName是以ping開頭的話,那麼將會輸出對應的內容。
看下執行結果:
> gradle -q pingServer1
Pinging: Server1
我還可以將這些rules作為依賴項引入:
task groupPing {
dependsOn pingServer1, pingServer2
}
Finalizer tasks
和java中的finally一樣,task也可以指定對應的finalize task:
task taskX {
doLast {
println 'taskX'
}
}
task taskY {
doLast {
println 'taskY'
}
}
taskX.finalizedBy taskY
> gradle -q taskX
taskX
taskY
finalize task是一定會被執行的,即使上面的taskX中丟擲了異常。
總結
以上就是gradle中task的詳解,希望大家能夠喜歡。
本文已收錄於 http://www.flydean.com/gradle-task-in-depth/
最通俗的解讀,最深刻的乾貨,最簡潔的教程,眾多你不知道的小技巧等你來發現!
歡迎關注我的公眾號:「程式那些事」,懂技術,更懂你!