【認識 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 的輔助教材,無論你參考書籍或官方文件,都能由此篇導讀獲得觀念釐清。
- 展示 task 關鍵字的用法
- 演示如何由 Javadoc 查詢出 task 語法對應至的方法
- 區辨建立 action 與 configure closure
- 簡介 task type
本文的目標在解析概念並做為實際學習 task 撰寫的補充教材,針對 task 本身的教學可使用 Gradle 官方手冊:
- 在 Gradle DSL 內的 Task 講解
- 在 User Guide 內的 Build Script Basic 有對 task 語法的描述與範例
- 在 User Guide 內的 Developing Custom Gradle Task Types,是更深入說明 task 的製作