任務結果標簽 當 Gradle 執行一個任務時,它會在控制台和 Tooling API 根據任務結果給任務打標簽。 這些標簽是根據任務是否有操作,是否應該執行操作,是否執行了操作以及這些操作做了哪些改變 來標記的。 下麵是 Gradle 的標簽以及對應的條件 (無標簽)或者 EXECUTED 任務執 ...
任務結果標簽
當 Gradle 執行一個任務時,它會在控制台和 Tooling API 根據任務結果給任務打標簽。
這些標簽是根據任務是否有操作,是否應該執行操作,是否執行了操作以及這些操作做了哪些改變 來標記的。
下麵是 Gradle 的標簽以及對應的條件
(無標簽)或者 EXECUTED
任務執行了它的操作。- 任務有操作並且 Gradle 已經決定作為構建的一部分來執行
- 任務沒有操作但有些依賴,並且執行了某些依賴項。參考下麵的生命周期任務。
UP-TO-DATE
任務輸出沒有變化- 任務有輸入和輸出,但沒有改變。另行參考增量構建
- 任務有操作,但是沒有改變它的輸出。
- 任務沒有操作但是有些依賴,但所有的依賴都是最新的,忽略的或者來自緩存。參考下麵的生命周期任務
FROM-CACHE
任務的輸出能夠在先前的任務執行中被找到- 任務輸出能夠從緩存中恢復。具體的可另行參考 構建緩存
SKIPPED
任務沒有執行它的操作- 任務已經明確的在命令行中被排除。
- 任務的 onlyIf 返回 false
NO-SOURCE
任務不需要執行它的操作- 任務有輸出和輸入,但是沒有來源。例如輸入是空的。
在Android studio 的 build console 就可以看到這個標簽
Tooling API 是指 Gradle 提供的編程 API,通常給 IDE,CI 伺服器使用的
創建任務
最簡單方便的定義方式:定義一個 hello 任務
這是使用 DSL 的語法
task hello {
doLast {
println 'Hello world!'
}
}
這裡的 task 看著像一個關鍵字,實際上是一個方法,這個方法的原型是 TaskContainer.create()
任務的創建就是使用這個方法給 Project 添加一個 Task 類型的屬性,名字就是我們的任務名字;所以才能使用任務名字引用一些API,例如為任務添加額外的屬性。
也可以使用一個字元串當做任務名稱,指定一個類型
task('hello') {
doLast {
println "hello"
}
}
task('copy', type: Copy) {
from(file('srcDir'))
into(buildDir)
}
或者是直接使用 tasks.create 方法; tasks 是 Project的一屬性 類型是 TaskContainer
tasks.create('hello') {
doLast {
println "hello"
}
}
tasks.create('copy', Copy) {
from(file('srcDir'))
into(buildDir)
}
任務配置
在創建任務的的時候,可以傳入參數對任務進行配置,比如 任務分組,任務描述等等
task(hello,group:'Hello',description:'這是一個 Hello。'){
doLast{
println "name:$name; group:$group; description:$description;"
}
}
輸出如下
> Task :hello
name:hello; group:Hello; description:這是一個 Hello。;
BUILD SUCCESSFUL in 694ms
也可以在閉包中對任務進行配置
task taskX{
group 'Hello'
description '在閉包里配置'
dependsOn hello
}
tasks.create('taskY',Copy){
description '使用 tasks.create()'
dependsOn hello
group 'Hello'
}
使用 gradle help --task 任務名
查看任務詳情
可以配置的參數如下
配置項 | 描述 | 預設值 |
---|---|---|
type | 基於一個存在的 Task 來創建,和我們的類繼承差不多 | DefaultTask |
dependsOn | 用於配置任務的依賴 | [] |
action | 添加到任務的一個 Action 或者一個閉包 | null |
description | 任務描述 | null |
group | 任務分組 | null |
任務訪問
通常是在配置任務或者是使用任務依賴的時候訪問任務。
下麵是幾種獲取任務引用的方式
使用 DSL 語法的方式
task hello
task copy(type: Copy)
// Access tasks using Groovy dynamic properties on Project
println hello.name
println project.hello.name
println copy.destinationDir
println project.copy.destinationDir
使用 tasks 集合訪問
task hello
task copy(type: Copy)
println tasks.hello.name
println tasks.named('hello').get().name
println tasks.copy.destinationDir
println tasks.named('copy').get().destinationDir
使用 tasks.getByPath() 方法
可以在構建文件的任何地方使用這個方法,它接受任務名字,任務相對路徑或者絕對路徑。
project(':projectA') {
task hello
}
task hello
println tasks.getByPath('hello').path
println tasks.getByPath(':hello').path
println tasks.getByPath('projectA:hello').path
println tasks.getByPath(':projectA:hello').path
輸出如下
gradle -q hello
:hello
:hello
:projectA:hello
:projectA:hello
當我們拿到這個任務的引用的時候,就可以按照我們的任務邏輯去操作它,比如配置任務依賴,配置任務的一些屬性,調用方法等。
添加操作
可以使用 Task.doLast 和 Task.doFirst 為任務添加操作
其中 doFirst 是在任務之前執行,doLast 在任務之後執行
task taskZ
taskZ.configure {
group 'Custom'
description '添加操作的實驗'
}
taskZ.doFirst {
println 'add doFirst. first'
}
taskZ.doLast {
println 'add doLast. first'
}
taskZ.doLast {
println 'add doLast. second'
}
taskZ.doFirst {
println 'add doFirst. second'
}
執行任務 gradle taskZ
gradle taskZ
> Task :taskZ
add doFirst. second
add doFirst. first
add doLast. first
add doLast. second
BUILD SUCCESSFUL in 608ms
從輸出可以看到是先執行 doFirst 然後是 doLast 。
執行分析
任務的執行其實就是執行它的 actions 列表。
這個列表保存在 Task 對象實例中的 actions 成員變數中,其類型是一個 List
private List<InputChangesAwareTaskAction> actions;
現在我們把 Task 之前執行、Task 本身執行以及 Task 之後執行分別稱為 doFirst,doSelf,doLast。下麵做個演示
class CustomTask extends DefaultTask{
@TaskAction
def doSelf(){
println 'Task 自己本身在執行 in doSelf.'
}
}
task taskA(type:CustomTask)
taskA.doFirst {
println "do First. Task 之前執行"
}
taskA.doLast {
println "do Last. Task 之後執行"
}
例子中定義了一個 CustomTask 並聲明瞭一個 doSelf 方法,使用 @TaskAction 標註,意思是這是任務本身要執行的方法。
執行 taskA 輸出
> Task :taskA
do First. Task 之前執行
Task 自己本身在執行 in doSelf.
do Last. Task 之後執行
BUILD SUCCESSFUL in 726ms
前面說過任務執行就是執行它的 actions List。那麼要達到這種 doFirst、doSelf、doLast 順序的目的,就必須把 doFirst 的 actions 放在 actions List 的前面,把 doSelf 的 actions 放在 List 的中間,把 doLast 的 actions 放在 List 最後面,這樣才能達到按約定順序執行的目的。
當我們使用 task 方法創建 taskA 這個任務的時候,Gradle 會解析其帶有 Task Action 標註的方法作為其 Task 執行的 Action,然後通過 Task 的 prependParallelSafeAction 方法把該 Action 添加到 actions List 里。
@Override
public void prependParallelSafeAction(final Action<? super Task> action) {
if (action == null) {
throw new InvalidUserDataException("Action must not be null!");
}
getTaskActions().add(0, wrap(action));
}
這個時候任務剛剛被創建,不會有 doFirst 的 Action, actions List 一般是空的。
只有在創建任務時,傳入了配置參數中的 action 選項配置的時候才會有。(上面配置任務有提到)
這個時候 actions List 就有了任務本身的 Action了。
再來看 doFirst 和 doLast 兩個方法的實現代碼
@Override
public Task doFirst(final Action<? super Task> action) {
return doFirst("doFirst {} action", action);
}
@Override
public Task doFirst(final String actionName, final Action<? super Task> action) {
hasCustomActions = true;
if (action == null) {
throw new InvalidUserDataException("Action must not be null!");
}
taskMutator.mutate("Task.doFirst(Action)", new Runnable() {
@Override
public void run() {
getTaskActions().add(0, wrap(action, actionName));
}
});
return this;
}
@Override
public Task doLast(final Action<? super Task> action) {
return doLast("doLast {} action", action);
}
@Override
public Task doLast(final String actionName, final Action<? super Task> action) {
hasCustomActions = true;
if (action == null) {
throw new InvalidUserDataException("Action must not be null!");
}
taskMutator.mutate("Task.doLast(Action)", new Runnable() {
@Override
public void run() {
getTaskActions().add(wrap(action, actionName));
}
});
return this;
}
看最重要的 actions.add 這部分
doFirst 永遠都是在 actions List 第一位添加,保證其添加的 Action 在現有的 actions List 的最前面。
doLast 永遠都是在 actions List 末尾添加,保證其添加的 Action 在現有的 actions List 元素的最後面。
一個往最前面添加,一個往最後面添加,最後這個 actions List 就形成了 doFirst,doSelf,doLast三部分的 actions.
也就保證了 doFirst,doSelf,doLast三部分的 Action 順序執行的目的。
這個任務執行分析在 《Android Gradle 權威指南》 中有很詳細的解釋。
任務排序
任務依賴也能夠達到讓任務排序的目的,但是還是有些區別的。
主要區別是排序不影響任務執行,隻影響執行順序。
有兩種排序規則 “must run after” and “should run after”.
taskA.mustRunAfter(taskB) 表示 taskA 必須總是在 TaskB 之後運行。
taskA.shouldRunAfter(taskB) 表示 taskA 應該在 taskB 之後運行,並不一定必須運行,沒有那麼嚴格。
should run after 不會被執行通常是在兩個場景下
- 陷入順序迴圈
- 在執行任務依賴的時候如果滿足了這個規則,將不會再次執行了。例如 再執行 taskB 的依賴時將 TaskA 給執行了,那麼在 taskB 完成後將不會再執行 taskA。
must run after
task taskX {
doLast {
println 'taskX'
}
}
task taskY {
doLast {
println 'taskY'
}
}
taskY.mustRunAfter taskX
> gradle -q taskY taskX
taskX
taskY
should run after
task taskX {
doLast {
println 'taskX'
}
}
task taskY {
doLast {
println 'taskY'
}
}
taskY.shouldRunAfter taskX
> gradle -q taskY taskX
taskX
taskY
如果只執行一個任務的話順序規則就沒用了
gradle -q taskY
taskY
任務跳過
Gradle 提供了多種方式跳過任務,任務被跳過將不會執行。
使用斷言 onlyIf
這個方法接收一個閉包參數,閉包返回 false 就不會執行,返回 true 將執行任務
這個方法是在執行任務前被調用的,不是在配置階段。
task hello {
doLast {
println 'hello world'
}
}
hello.onlyIf { !project.hasProperty('skipHello') }
gradle hello -PskipHello
> Task :hello SKIPPED
BUILD SUCCESSFUL in 0s
使用 StopExecutionException
如果斷言不能滿足你的話,就可以使用這個 StopExecutionException ,使用這個異常可以執行時根據邏輯進行判斷。
這個異常可以在一個操作中拋出,拋出後直接跳過這個任務進行下一個任務。
task compile {
doLast {
println 'We are doing the compile.'
}
}
compile.doFirst {
// Here you would put arbitrary conditions in real life.
// But this is used in an integration test so we want defined behavior.
if (true) { throw new StopExecutionException() }
}
task myTask {
dependsOn('compile')
doLast {
println 'I am not affected'
}
}
gradle -q myTask
I am not affected
使用 enabled = false
每個任務都有一個 enabled 標誌,預設是 true,如果設置為 false 這個任務將會被標記為 SKIPPED,直接跳過
task disableMe {
doLast {
println 'This should not be printed if the task is disabled.'
}
}
disableMe.enabled = false
執行 gradle disableMe
gradle disableMe
Task :disableMe SKIPPED
BUILD SUCCESSFUL in 0s
使用 超時時間
每個任務都有一個 timeout 屬性用來限制執行時間。
當任務執行超時,任務執行線程就會被終止,任務將會被標記失敗。
如果使用了 --continues 其他任務將會繼續執行。
如果任務不能響應超時,任務將不會被終止。
Gradle 所有的內置任務都會響應超時。
task hangingTask() {
doLast {
Thread.sleep(100000)
}
timeout = Duration.ofMillis(500)
}
任務規則
正常情況下在使用 gradle 執行任務時,如果任務不存在就會拋出異常。
而任務規則就是在 Gradle 找不到任務時應用的規則,例如我們可以在找不到任務時列印一句話或者執行其他操作。
任務規則的添加和創建一樣都是由 TaskContainer 完成,其實這個實在 NamedDomainObjectCollection 介面中的,不過 TaskContainer 繼承了這個介面。
tasks.addRule("這就是一個任務規則描述"){ taskName ->
task (taskName){
println "$taskName 不存在"
}
}
這個 方法有多個重載,詳情可查看 API。
API 傳送門
生命周期任務
生命周期任務通常是沒有操作的,通常是表達一個概念,例如下麵幾個:
- 一個步驟,例如 check 檢查,build 構建;
- 一個可構建的東西,例如一個可執行文件
- 一個組合了多個邏輯任務的空殼任務,例如 使用 compileAll 任務執行所有編譯任務
Base Plugin 中定義了標準生命周期任務:
- check
- assemble
- build
幾乎所有的核心語言插件都應用了 Base Plugin, 例如 Java Plugin。
所以它的生命周期任務幾乎所有語言插件都有。
除非生命周期任務有操作,否則它的結果標記應該是由它的依賴的執行結果決定的。
如果所有的依賴都被執行了,那麼就應該標記 EXECUTED
如果所有的依賴都是最新的,跳過的或來自緩存,那麼就應該被標記為 UP-TO-DATE