【認識 Gradle】(5)Gradle Task 觀念導讀

- gradle-series

初學撰寫 Gradle Script 焦點多著在撰寫 task,透過寫自有的 task 將專案適度地客製化。Gradle Task 『補充教材』將目標放在 task 的語法與內建 task 的運用。Gradle Task 的概念近似於 Ant Target,不過 Gradle 是 DSL 而不是像 Ant 般地 XML 描述資料,它在 Task 的操作上就如同程式語言的撰寫。Gradle Task 其實是 Action 的容器,實際執行動作的邏輯會被 Gralde 由 Groovy Closure 轉換成 Action 物件,它就放置在 Gradle Task 內部。

對 Gradle DSL 來說,task 扮演程式語言內關鍵字的角色,我們用它建立新的 Gradle Task。官方手冊已有淺顯意懂的教學,觀念導讀的目標在於講述自我學習經驗中曾誤解過的概念,由 task 的寫法下手並運用上一篇學習到的手法對 task 關鍵字的用法做進一步的剖析。

解讀 Gradle Task

在許多 Gradle 的教學內,最先介紹是的怎麼寫出一個 Task,依慣例在前幾章會看到滿滿的 Hello World:

task hello << {
    println 'Hello World'
}

也可以這樣寫:

task hello {
    doLast {
        println 'Hello world!'
    }
}

也可以寫成這樣(不過這樣寫的『語意』不同於上述二者,後續會再討論):

task hello {
    println 'Hello World'
}

這些寫法部分來自書本,部分來自 Gradle Getting Started 的 Build Script Basics。一個 task 搞了那麼多寫法,對剛開始學習 Gradle 的人來說會產生混亂。這也是為何上一單元要介紹如何看懂 Gradle Script 的目的,運用那份知識我們能再次解讀 Gradle Task。首先由文件查出有沒有加 « 符號的差異:

task hello << { }

task hello { }

讀者是否能由 Project 的 Javadoc 查出它們分別對應至哪個方法呢?

對有使用 << 運算子的 task 來說,它是接收一個 name 參數回傳 Task 物件,再呼叫 << 對應的方法;對於沒有使用 << 運算子的 task 來說是接受二個參數,第一個為 name,第二個為 Groovy Closure。

Groovy 是支援運算子多載的語言,也就是能重新定義一個物件配合特定運算子使用的功能。以 << 運算子來說,Task 物件需要重新定義 leftshift 方法。閱讀 Task 物件的 Javadoc 時,能發現到另一個方法 doLast 它的功能描述與 leftshift 幾乎一樣,只差在多了對於 << 運算子的語法描述:

由上面的資訊可以知道使用 << 的寫法為何比起 doLast 來算算是 task definition shortcut,看起來簡潔也少打了許多字,還用上了 << 似乎很新潮的樣子。那麼在 task 使用的語意上有差別的其實是不加 << 的情況:

task hello { }

註:文章寫於 2013 年,重新上架於 2021 年:task definition shortcut 已經列為將棄用的功能。

先前的 << 或 doLast 或是其他文件有提到的 doFirst 它們後面接的 Closure 參數都是 action,即為一開始介紹的 Gradle Task 是由多個 Action 組成,在不加 << 的情況它並不是 Action,而被稱為 configureClosure,這個 Closure 主要的功能是確定 delegate 的對象是 Task 物件,所以在這個 Closure 內呼叫的方法都是針對 delegate 對象來做的,這也是為什麼在 Closure 內可以呼叫 doLast 方法。這件事同樣能被簡單地驗證:

task hello {
    println delegate.class.name
    println delegate instanceof Task
}
qty:gradleLab qrtt1$ gradle -q hello
org.gradle.api.DefaultTask_Decorated
true

在設計上,這個 Closure 是用來設定 task 內容,它並不是 Action。差別是什麼?Action 要明確呼被 task 時才會呼叫,而 configure 動作在初始化 Task 時就會執行。差別就在若是將應該執行的內容寫在 configure 內時,它就會在你非預期的情況時執行。直接看這個例子:

task hello {
    println "Hello World"
}

請試著呼叫一個不存在的 task,你會看到 Hello World 被印出來,這是因為它是一個 configureClosure。印出 Hello World 是在 Task 物件初始化的階段,它並不是 Action:

qty:gradleLab qrtt1$ gradle -q noSuchTaskname
Hello World

FAILURE: Could not determine which tasks to execute.

* What went wrong:
Task 'noSuchTaskname' not found in root project 'gradleLab'.

* Try:
Run gradle tasks to get a list of available tasks.

所以,我們在 task 名稱後加上 << 並不是為了看起來炫,而這才是建立 Action 的正確語意。

Task Type

Gradle 提供不同的語法建立 task,而不加 << 時是設定(configure) Task 物件之用,那麼它會被用在哪呢?除了寫自己的 task 之外,還可以使用 Gradle 提供不同的 task。預設情況它會產生 DefaultTask 物件,我們可以指定產生不同型態的 Task 物件。Gradle 內建的 Task 可在 DSL 文件內的 Task types 查到。使用內建的 Task 它多半設計成只需要簡單設定後就能使用,以 Copy Task 提供的例子來說:

task mydoc(type:Copy) {
    from 'src/main/doc'
    into 'build/target/doc'
}

這是一個名為 mydoc 的 task,它會被建立成 Copy Task 物件,而 Closure 的 delegate 即為這個物件,能使用 from 與 into 方法指定 Copy 被執行時的來源與目的。

舉個例來說,一般在編譯 Java 專案時,只會有 classes 包起來的 JAR 檔,我們可以讓它一併編出 Javadoc 與 Source JARs:

apply plugin: 'java'

task sourcesJar(type: Jar, dependsOn:classes) {
     classifier = 'sources'
     from sourceSets.main.allSource
}

task javadocJar(type: Jar, dependsOn:javadoc) {
     classifier = 'javadoc'
     from javadoc.destinationDir
}

artifacts {
     archives sourcesJar
     archives javadocJar
}

主要是利用 Project 的 artifacts 加入新的 archives 動作,它對應到另外二個 Jar Task,在 Jar Task 內做了適當的設定,最後它會將相關的 JARs 產生出來。

內容回顧

Gradle Task 還有著更多的細節,本篇針對 DSL 討論將常被人誤用或理解模稜兩可之處點題。能將這篇的內容當作學習 Gradle Task 的輔助教材,無論你參考書籍或官方文件,都能由此篇導讀獲得觀念釐清。

  1. 展示 task 關鍵字的用法
  2. 演示如何由 Javadoc 查詢出 task 語法對應至的方法
  3. 區辨建立 action 與 configure closure
  4. 簡介 task type

本文的目標在解析概念並做為實際學習 task 撰寫的補充教材,針對 task 本身的教學可使用 Gradle 官方手冊:

  1. 在 Gradle DSL 內的 Task 講解
  2. 在 User Guide 內的 Build Script Basic 有對 task 語法的描述與範例
  3. 在 User Guide 內的 Developing Custom Gradle Task Types,是更深入說明 task 的製作