Gradle系列之認識Gradle任務

躬行之發表於2020-08-15

原文發於微信公眾號 jzman-blog,歡迎關注交流。

前面幾篇學習了 Gradle 構建任務的基礎知識,瞭解了 Project 和 Task 這兩個概念,建議先閱讀前面幾篇文章:

Gradle 的構建工作是有一系列的 Task 來完成的,本文將針對 Task 進行詳細介紹,本文主要內容如下:

  1. 多種方式建立任務
  2. 多種方式訪問任務
  3. 任務分組和描述
  4. 操作符
  5. 任務的執行分析
  6. 任務排序
  7. 任務的啟用和禁用
  8. 任務的onlyIf斷言
  9. 任務規則

多種方式建立任務

Gradle 中可以使用多種方式來建立任務,多種建立任務的方式最終反映在 Project 提供的快捷方法以及內建的 TaskContainer 提供的 create 方法,下面是幾種常見的建立任務的方式:

/**
 * 第一種建立任務方式:
 * 方法原型:Task task(String name) throws InvalidUserDataException;
 */
//定義Task變數接收task()方法建立的Task,方法配置建立的Task
def Task taskA = task(taskA)
//配置建立的Task
taskA.doFirst {
    println "第一種建立任務的方式"
}

/**task
 * 第二種建立任務方式:可在Map引數中進行相關配置,如依賴、任務描述、組別等
 * 方法原型:Task task(Map<String, ?> args, String name) throws InvalidUserDataException;
 */
def Task taskB = task(group: BasePlugin.BUILD_GROUP,taskB,description: "描述")
//配置建立的Task
taskB.doLast {
    println "第二種建立任務的方式"
    println "任務taskB分組:${taskB.group}"
    println "任務taskB描述:${taskB.description}"
}

/**
 * 第三種建立任務方式:通過閉包的方式建立Task,閉包裡的委託物件就是Task,即可在閉包內呼叫Task
 * 的一切屬性和方法來進行Task的配置
 * 方法原型:Task task(String name, Closure configureClosure);
 */
task taskC{
    description 'taskC的描述'
    group BasePlugin.BUILD_GROUP
    doFirst{
        println "第三種建立任務的方式"
        println "任務taskC分組:${group}"
        println "任務taskC描述:${description}"
    }
}

/**
 * 第四種建立任務的方式:可在閉包中靈活配置,也可在Map引數中配置,閉包中中的配置父覆蓋Map中相同的配置
 * 方法原型:Task task(Map<String, ?> args, String name, Closure configureClosure);
 */
def Task taskD = task(group: BasePlugin.BUILD_GROUP,taskD,description: "描述"){
    description 'taskD的描述'
    group BasePlugin.UPLOAD_GROUP
    doFirst{
        println "第四種建立任務的方式"
        println "任務taskD分組:${group}"
        println "任務taskD描述:${description}"
    }
}

上面是建立任務的四種方式,使用時選擇合適的建立方式即可,上面提到 Map 中可以配置 Task 的相關引數,下面是是 Map 中可使用的配置:

type:基於一個已存在的Task來建立,類似於類的繼承,預設值DefaultTask
overwrite:是否替換存在的Task,一般和type配合使用,預設值false
dependsOn:配置當前任務的依賴,預設值[]
action:新增到任務中的一個Action或者是一個閉包,預設值為null
description:任務描述,預設值null
group:任務分組,預設值null

通過閉包的方式建立 Task,閉包裡的委託物件就是 Task,即可在閉包內呼叫 Task
的一切屬性和方法來進行 Task 的配置,可以說使用閉包的這種任務建立方式更靈活,此外還可以使用 TaskContainer 建立任務,參考如下:

//使用TaskContainer建立任務的方式
tasks.create("taskE"){
    description 'taskE的描述'
    group BasePlugin.BUILD_GROUP
    doFirst{
        println "第三種建立任務的方式"
        println "任務taskE分組:${group}"
        println "任務taskE描述:${description}"
    }
}

tasks 是 Project 的屬性,其型別是 TaskContainer,所以可以通過 tasks 來建立任務,當然 TaskContainer 建立任務也有建立任務的其他構造方法,到此關於任務的建立就基本介紹完了。

多種方式訪問任務

建立的任務(Task)屬於專案(Project)的一個屬性,其屬性名就是任務名,該屬性的型別是 Task,如果已知任務名稱,那麼可以通過任務名直接訪問和操縱該任務了,也可以理解訪問和操縱該任務所對應的 Task 物件,參考
如下:

/**
 * 訪問任務的第一種方式:Task名稱.doLast{}
 */
task taskF{

}
taskF.doLast{
    println "第一種訪問任務的方式"
}

任務都是通過 TaskContainer 的 create 方法建立的,而 TaskContainer 是建立任務的集合,在 Project 中可通過 tasks 屬性訪問 TaskContainer ,tasks 的型別就是 TaskContainer,所以對於已經建立的任務可通過訪問幾何元素的方式訪問已建立任務的屬性和方法,參考程式碼如下:

/**
 * 訪問任務的第二種方式:使用TaskContainer訪問任務
 */
task taskG{

}
tasks['taskG'].doLast {
    println "第二種訪問任務的方式"
}

在 Groovy 中 [] 也是一個操作符,上面 tasks['taskG'] 的真正含義是 tasks.getAt('taskG') , getAt() 方法在 TaskCollection 中的方法,這樣可以通過任務名稱對相關任務進行訪問和操作。

還可以通過路徑訪問的方式訪問任務,通過路徑訪問任務有兩個關鍵方法:findByPath 和 getByPath,區別在於前者找不到指定任務的時候會返回 null,後者找不到任務的時候會丟擲 UnknowTaskException 異常,程式碼參考如下:

/**
 * 訪問任務的第三種方式:使用路徑訪問任務
 */
task taskH{
    println 'taskH'
    //通過路徑訪問任務,引數可以是路徑(沒有訪問成功,寫法如下)
    println tasks.findByPath(':GradleTask:taskG')
    //通過路徑訪問任務,引數可以是任務名稱
    println tasks.findByPath('taskG')
    println tasks.getByPath('taskG')
}

上述程式碼執行結果參考如下:

PS E:\Gradle\study\GradleTask> gradle taskH

> Configure project :
taskH
null
task ':taskG'
task ':taskG'


FAILURE: Build failed with an exception.

* Where:
Build file 'E:\Gradle\study\GradleTask\build.gradle' line: 98

* What went wrong:
A problem occurred evaluating root project 'GradleTask'.
> Task with path 'test' not found in root project 'GradleTask'.

* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output.

* Get more help at https://help.gradle.org

BUILD FAILED in 2s

使用路徑訪問任務的過程,引數寫成路徑訪問不到具體的任務,可能是寫法問題,希望在後面的學習中能夠解決。

此外,還可以通過任務名稱訪問,方法主要是 findByName 和 getByName,區別和第三種訪問方式一樣, 程式碼參考如下:

/**
 * 訪問任務的第四種方式:使用任務名稱訪問
 */
task taskJ
tasks['taskJ'].doLast{
    println 'taskJ'
    println tasks.findByName('taskJ')
    println tasks.getByName('taskJ')
}

上面學習了訪問任務的四種方式,通過對 Gradle 訪問任務的瞭解,在具體的專案構建上在結合上面訪問任務的方式靈活使用。

任務分組和描述

對於任務分組及描述實際上在之前的文章已經提到過且配置過,這裡再簡單說明一下,任務分組和描述實際上就是對已經建立的任務配置分組和任務描述,如下面這樣配置:

//任務分組與描述
def Task task1 = task taskK
task1.group = BasePlugin.BUILD_GROUP
task1.description = '測試任務分組與描述'
task1.doLast {
    println "taskK is group = ${group}, description = ${description}"
}

下面是上述程式碼執行結果,參考如下:

PS E:\Gradle\study\GradleTask> gradle taskK

> Task :taskK
taskK is group = build, description = 測試任務分組與描述


BUILD SUCCESSFUL in 2s
1 actionable task: 1 executed

從執行結果可以看出,如果配置了任務的相關屬性,那麼就可以訪問到任務的所有資訊了。

操作符

學習一個操作符 << ,之前的測試程式碼中為了測試都會呼叫 task.doLast() 方法,我們可以使用 << 操作符來代替 doLast 方法,也就是說 daLast() 方法可以這樣寫:

//<< 任務操作符
//簡寫方式,Gradle 5.0 開始以不推薦這種寫法
task taskL <<{
    println "doLast"
}
//推薦寫法
task taskL{
    doLast{
        println "doLast"
    }
}

上述兩種寫法的執行結果參考如下:

PS E:\Gradle\study\GradleTask> gradle taskL

> Configure project :
The Task.leftShift(Closure) method has been deprecated and is scheduled to be removed in Gradle 5.0. Please use Task.doLast(Action) instead.
        at build_6syzx8ks0l09hby4j6yn247u9.run(E:\Gradle\study\GradleTask\build.gradle:123)

> Task :taskL
doLast


BUILD SUCCESSFUL in 2s
1 actionable task: 1 executed
PS E:\Gradle\study\GradleTask> gradle taskL

> Task :taskL
doLast


BUILD SUCCESSFUL in 2s
1 actionable task: 1 executed
PS E:\Gradle\study\GradleTask>

從輸出結果可以看到兩種寫法都輸出了想要的結果,同時觀察日誌發現這種簡寫方式已經在 Gradle 5.0 開始已經被放棄,所以推薦搭建使用 doLast 的方式配置任務。

任務的執行分析

在 Gradle 任務執行過程中,我們可以通過 doFirst 和 doLast 在任務執行之前或執行之後進行任務相關配置,當我們執行一個任務的時候,實際上是在執行該 Task 所擁有的 action,可以通過 getActions() 方法獲取所有可以執行的 action,下面自定義一個 Task 型別 CustomTask ,並使用註解 @TaskAction 標註方法 doSelf 表示 Task 本身要執行的方法,程式碼如下:

//任務執行流程分析
def Task taskA = task taskB(type: CustomTask)
taskA.doFirst {
    println "Task執行之前呼叫:doFirst"
}

taskA.doLast {
    println "Task執行之後呼叫:doLast"
}

class CustomTask extends DefaultTask{
    @TaskAction
    def doSelf(){
        println "Task執行本身呼叫:doSelf"
    }
}

上述程式碼的執行結果如下:

PS E:\Gradle\study\GradleTask2> gradle taskB

> Task :taskB
Task執行之前呼叫:doFirst
Task執行本身呼叫:doSelf
Task執行之後呼叫:doLast


BUILD SUCCESSFUL in 2s
1 actionable task: 1 executed

由於 Task 的執行是在遍歷需要執行的 action 列表,為了保證執行的順序,則必須將 doFirst 對應的 action 放在 action 列表的最前面,doLast 對應的 action 放在 action 列表的最後面,doSelf 對應的 action 放置在列表的中間位置,這樣就能保證對應的執行順序了,下面是虛擬碼:

//建立任務的時候將使用@TaskAction標註的方法作為Task本身執行的Task
//此時,任務正在建立,actionList裡面只有Task本身執行的Action
actionList.add(0,doSelfAction)
//任務建立完成之後,如果設定了doFirst則會在actionList最前面新增doFist對應的action
//此時,doFirst對應的action新增actionList的最前面,保證了doFirst方法在任務開始執行之前執行
actionList.add(0,doFirstAction)
//任務建立完成之後,如果設定了doLast則會在actionList最後面新增doLast對應的action,保證了doLast方法在任務開始執行之後執行
actionList.add(doLastAction)

任務執行的流程基本如上,儘量在具體實踐中多體會。

任務排序

Gradle 中任務排序使用到的是 Task 的兩個方法 shoundRunAfter 和 mustRunAfter,可以方便的控制兩個任務誰先執行:

/**
 * 任務順序
 * taskC.shouldRunAfter(taskD):表示taskC要在taskD的後面執行
 * taskC.mustRunAfter(taskD):表示taskC必須要在taskD的後面執行
 */
task taskC  {
    doFirst{
        println "taskC"
    }
}
task taskD  {
    doFirst{
        println "taskD"
    }
}
taskC.shouldRunAfter(taskD)

上述程式碼的執行結果,參考如下:

PS E:\Gradle\study\GradleTask2> gradle taskC taskD

> Task :taskD
taskD

> Task :taskC
taskC


BUILD SUCCESSFUL in 2s
2 actionable tasks: 2 executed

任務的啟用和禁用

Task 中有個 enabled 屬性,可以使用該屬性啟用和禁用某個任務,設定為 true 則啟用該任務,反之則禁用該任務,該屬性預設為 true,使用如下所示:

taskA.enabled = true

任務的onlyIf斷言

斷言是一個條件表示式, Task 物件有一個 onlyIf 方法,該方法可以接收一個閉包作為引數,如果該閉包內引數返回 true,則該任務執行,反之則不執行該任務,這樣可以通過任務的斷言來控制那些任務需要執行,下面通過一個打包的案列來學習任務的斷言,程式碼參考如下:

//任務的onlyIf斷言
final String BUILD_ALL = 'all'
final String BUILD_FIRST = 'first'
final String BUILD_OTHERS = 'others'

task taskTencentRelease{
    doLast{
        println "打應用寶渠道包"
    }
}

task taskBaiduRelease{
    doLast{
        println "打百度手機助手渠道包"
    }
}

task taskMiuiRelease{
    doLast{
        println "打小米應用商店渠道包"
    }
}

task buildTask{
    group BasePlugin.BUILD_GROUP
    description "打渠道包"
}

//為buildTask新增依賴的具體任務
buildTask.dependsOn taskTencentRelease, taskBaiduRelease, taskMiuiRelease

taskTencentRelease.onlyIf{
    if (project.hasProperty("buildApp")){
        Object buildApp = project.property("buildApp")
        return BUILD_ALL == buildApp || BUILD_FIRST == buildApp
    }else{
        return true
    }
}

taskBaiduRelease.onlyIf{
    if (project.hasProperty("buildApp")){
        Object buildApp = project.property("buildApp")
        return BUILD_ALL == buildApp || BUILD_FIRST == buildApp
    }else{
        return true
    }
}

taskMiuiRelease.onlyIf{
    if (project.hasProperty("buildApp")){
        Object buildApp = project.property("buildApp")
        return BUILD_OTHERS == buildApp || BUILD_ALL == buildApp
    }else{
        return true
    }
}

下面是上述程式碼的執行結果:

PS E:\Gradle\study\GradleTask2> gradle -PbuildApp=first buildTask

> Task :taskBaiduRelease
打百度手機助手渠道包

> Task :taskTencentRelease
打應用寶渠道包


BUILD SUCCESSFUL in 1s
2 actionable tasks: 2 executed
PS E:\Gradle\study\GradleTask2> gradle -PbuildApp=others buildTask

> Task :taskMiuiRelease
打小米應用商店渠道包


BUILD SUCCESSFUL in 1s
1 actionable task: 1 executed
PS E:\Gradle\study\GradleTask2> gradle -PbuildApp=all buildTask

> Task :taskBaiduRelease
打百度手機助手渠道包

> Task :taskMiuiRelease
打小米應用商店渠道包

> Task :taskTencentRelease
打應用寶渠道包


BUILD SUCCESSFUL in 1s
3 actionable tasks: 3 executed

可以看出,當我們執行 buildTask 時為 Project 配置了屬性 buildApp,通過 buildApp 不同的值,藉助 onlyIf 實現了不同渠道包的定製打包策略,可在實際開發中借鑑加以使用。

此外,注意上述程式碼執行命令的寫法,具體如下:

//其中buildApp和=後面的值others是鍵值對的關係,使用命令執行任務時可使用-P命令簡寫
//-P要為當前Project指定K-V的屬性鍵值對,即-PK=V
gradle -PbuildApp=others buildTask

任務規則

建立的任務都是在 TaskContain 裡面,我麼可以通過從 Project 的屬性 tasks 中根據任務的名稱來獲取想要獲取的任務,可以通過 TaskContain 的 addRule 方法新增相應的任務規則,參考程式碼如下:

//任務規則
tasks.addRule("對該規則的一個描述"){
    //在閉包中常常將->作為引數與程式碼塊之間的分隔符
    String taskName ->
        task(taskName) {
            doLast{
                println "${taskName} 不存在"
            }
        }
}

task taskTest{
    dependsOn taskX
}

上述程式碼的執行結果:

PS E:\Gradle\study\GradleTask2> gradle taskTest

> Task :taskX
taskX 不存在


BUILD SUCCESSFUL in 1s
1 actionable task: 1 executed

如果不指定對某些特殊情況的任務處理,則會報錯,如果處理了則會輸出相關的提示資訊,Gradle 任務的瞭解和學習就到此為止。可以關注公眾號:零點小築(jzman-blog),一起交流學習。

相關文章