Gradle 1.12 翻譯——第十五章. 任務詳述

有關其它已翻譯的章節請關注Github上的項目:https://github.com/msdx/gradledoc/tree/1.12。或訪問:http://gradledoc.qiniudn.com/1.12/userguide/userguide.htmlhtml

本文原創。轉載請註明出處:http://blog.csdn.net/maosidiaoxian/article/details/41038305java

關於我對Gradle的翻譯。以Github上的項目及http://gradledoc.qiniudn.com 上的文檔爲準。git

若有發現翻譯有誤的地方,將首先在以上兩個地方更新。因時間精力問題,博客中發表的譯文基本不會同步改動。github


第十五章. 任務詳述

在新手教程 (第 6 章。構建腳本基礎) 中。你已經學習了怎樣建立簡單的任務。以後您還學習了怎樣將其它行爲加入到這些任務中。並且你已經學會了怎樣建立任務之間的依賴。這都是簡單的任務。但 Gradle 讓任務的概念更深遠。Gradle 支持加強的任務,也就是。有本身的屬性和方法的任務。這是真正的與你所使用的 Ant 目標(target)的不一樣之處。這樣的加強的任務可以由你提供,或由 Gradle 提供。web

15.1. 定義任務

第 6 章。構建腳本基礎 中咱們已經看到怎樣經過keyword這樣的風格來定義任務。編程

在某些狀況中,你可能需要使用這樣的keyword風格的幾種不一樣的變式。api

好比,在表達式中不能用這樣的keyword風格。閉包

演示樣例 15.1. 定義任務ide

build.gradlepost

task(hello) << {
    println "hello"
}

task(copy, type: Copy) {
    from(file('srcDir'))
    into(buildDir)
}

您還可以使用字符串做爲任務名稱:

演示樣例 15.2. 定義任務 — — 使用字符串做爲任務名稱

build.gradle

task('hello') <<
{
    println "hello"
}

task('copy', type: Copy) {
    from(file('srcDir'))
    into(buildDir)
}

對於定義任務。有一種替代的語法你可能更願意使用:

演示樣例 15.3. 使用替代語法定義任務

build.gradle

tasks.create(name: 'hello') << {
    println "hello"
}

tasks.create(name: 'copy', type: Copy) {
    from(file('srcDir'))
    into(buildDir)
}

在這裏咱們將任務加入到tasks集合。關於create ()方法的不少其它變化可以看看TaskContainer

15.2. 定位任務

你經常需要在構建文件裏查找你所定義的任務。好比,爲了去配置或是依賴它們。對這種狀況。有很是多種方法。首先,每個任務均可做爲項目的一個屬性,並且使用任務名稱做爲這個屬性名稱:

演示樣例 15.4. 以屬性方式訪問任務

build.gradle

task hello

println hello.name
println project.hello.name

任務也可以經過tasks集合來訪問。

演示樣例 15.5. 經過tasks集合訪問任務

build.gradle

task hello

println tasks.hello.name
println tasks['hello'].name

您可以從不論什麼項目中,使用tasks.getByPath()方法獲取任務路徑並且經過這個路徑來訪問任務。

你可以用任務名稱,相對路徑或者是絕對路徑做爲參數調用getByPath()方法。

演示樣例 15.6. 經過路徑訪問任務

build.gradle

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的輸出結果

> gradle -q hello
:hello
:hello
:projectA:hello
:projectA:hello

有關查找任務的不少其它選項,可以看一下TaskContainer

15.3. .配置任務

做爲一個樣例,讓咱們看看由 Gradle 提供的Copy任務。若要建立Copy任務,您可以在構建腳本中聲明:

演示樣例 15.7. 建立一個複製任務

build.gradle

task myCopy(type: Copy)

上面的代碼建立了一個什麼都沒作的複製任務。

可以使用它的 API 來配置這個任務 (見Copy)。如下的演示樣例演示了幾種不一樣的方式來實現一樣的配置。

演示樣例 15.8. 配置任務的幾種方式

build.gradle

Copy myCopy = task(myCopy, type: Copy)
myCopy.from 'resources'
myCopy.into 'target'
myCopy.include('**/*.txt', '**/*.xml', '**/*.properties')

這相似於咱們一般在 Java 中配置對象的方式。您必須在每一次的配置語句反覆上下文 (myCopy)。這顯得很是冗餘並且很是很差讀。

還有還有一種配置任務的方式。它也保留了上下文,且可以說是可讀性最強的。

它是咱們一般最喜歡的方式。

演示樣例 15.9. 配置任務-使用閉包

build.gradle

task myCopy(type: Copy)

myCopy {
   from 'resources'
   into 'target'
   include('**/*.txt', '**/*.xml', '**/*.properties')
}

這樣的方式適用於不論什麼任務。該樣例的第 3 行僅僅是tasks.getByName()方法的簡潔寫法。特別要注意的是。假設您向getByName()方法傳入一個閉包,這個閉包的應用是在配置這個任務的時候,而不是任務運行的時候。

您也可以在定義一個任務的時候使用一個配置閉包。

演示樣例 15.10. 使用閉包定義任務

build.gradle

task copy(type: Copy) {
   from 'resources'
   into 'target'
   include('**/*.txt', '**/*.xml', '**/*.properties')
}

15.4. 對任務加入依賴

定義任務的依賴關係有幾種方法。

第 6.5 章節。"任務依賴"中。已經向你介紹了使用任務名稱來定義依賴。任務的名稱可以指向同一個項目中的任務,或者其它項目中的任務。要引用還有一個項目中的任務。你需要把它所屬的項目的路徑做爲前綴加到它的名字中。如下是一個演示樣例,加入了從projectA:taskXprojectB:taskY的依賴關係:

演示樣例 15.11. 從還有一個項目的任務上加入依賴

build.gradle

project('projectA') {
    task taskX(dependsOn: ':projectB:taskY') << {
        println 'taskX'
    }
}

project('projectB') {
    task taskY << {
        println 'taskY'
    }
}

gradle -q taskX的輸出結果

> gradle -q taskX
taskY
taskX

您可以使用一個Task對象而不是任務名稱來定義依賴。例如如下:

演示樣例 15.12. 使用 task 對象加入依賴

build.gradle

task taskX << {
    println 'taskX'
}

task taskY << {
    println 'taskY'
}

taskX.dependsOn taskY

gradle -q taskX的輸出結果

> gradle -q taskX
taskY
taskX

對於更高級的使用方法。您可以使用閉包來定義任務依賴。在計算依賴時,閉包會被傳入正在計算依賴的任務。

這個閉包應該返回一個 Task 對象或是Task 對象的集合。返回值會被做爲這個任務的依賴項。如下的演示樣例是從taskX增長了項目中所有名稱以lib開頭的任務的依賴:

演示樣例 15.13. 使用閉包加入依賴

build.gradle

task taskX << {
    println 'taskX'
}

taskX.dependsOn {
    tasks.findAll { task -> task.name.startsWith('lib') }
}

task lib1 << {
    println 'lib1'
}

task lib2 << {
    println 'lib2'
}

task notALib << {
    println 'notALib'
}

gradle -q taskX的輸出結果

> gradle -q taskX
lib1
lib2
taskX

有關任務依賴的具體信息,請參閱Task的 API。

15.5. 任務排序

任務排序仍是一個孵化中的功能。請注意此功能在之後的 Gradle 版本號中可能會改變。

在某些狀況下。控制兩個任務的運行的順序,而不引入這些任務之間的顯式依賴。是很是實用的。

任務排序和任務依賴之間的主要差異是,排序規則不會影響那些任務的運行,而僅將運行的順序。

任務排序在不少狀況下可能很是實用:

  • 強制任務順序運行: 如,'build' 永遠不會在 'clean' 前面運行。
  • 在構建中儘早進行構建驗證:如,驗證在開始公佈的工做前有一個正確的證書。

  • 經過在長久驗證前執行高速驗證以獲得更快的反饋:如,單元測試應在集成測試以前執行。
  • 一個任務聚合了某一特定類型的所有任務的結果:如,測試報告任務結合了所有運行的測試任務的輸出。

有兩種排序規則是可用的:"必須在以後執行"和"應該在以後執行"。

經過使用 「 必須在以後執行」的排序規則。您可以指定 taskB 必須老是執行在 taskA 以後。無論taskAtaskB這兩個任務在何時被調度執行。這被表示爲 taskB.mustRunAfter(taskA) 。「應該在以後執行」的排序規則與其相似,但沒有那麼嚴格,因爲它在兩種狀況下會被忽略。首先是假設使用這一規則引入了一個排序循環。其次,當使用並行執行,並且一個任務的所有依賴項除了任務應該在以後執行以外所有條件已知足,那麼這個任務將會執行,無論它的「應該在以後執行」的依賴項是否已經執行了。

當傾向於更快的反饋時,會使用「應該在以後執行」的規則,因爲這樣的排序很是有幫助但要求不嚴格。

眼下使用這些規則仍有可能出現taskA運行而taskB 沒有運行,或者taskB運行而taskA 沒有運行。

演示樣例 15.14. 加入 '必須在以後執行 ' 的任務排序

build.gradle

task taskX << {
    println 'taskX'
}
task taskY << {
    println 'taskY'
}
taskY.mustRunAfter taskX

gradle -q taskY taskX 的輸出結果

> gradle -q taskY taskX
taskX
taskY

演示樣例 15.15. 加入 '應該在以後執行 ' 的任務排序

build.gradle

task taskX << {
    println 'taskX'
}
task taskY << {
    println 'taskY'
}
taskY.shouldRunAfter taskX

gradle -q taskY taskX 的輸出結果

> gradle -q taskY taskX
taskX
taskY

在上面的樣例中。它仍有可能執行taskY而不會致使taskX也執行:

演示樣例 15.16. 任務排序並不意味着任務運行

gradle -q taskY 的輸出結果

> gradle -q taskY
taskY

假設想指定兩個任務之間的「必須在以後執行」和「應該在以後執行」排序。可以使用Task.mustRunAfter()Task.shouldRunAfter()方法。

這些方法接受一個任務實例、 任務名稱或Task.dependsOn()所接受的不論什麼其它輸入做爲參數。

請注意"B.mustRunAfter(A)"或"B.shouldRunAfter(A)"並不意味着這些任務之間的不論什麼運行上的依賴關係:

  • 它是可以獨立地運行任務AB 的。排序規則僅在這兩項任務計劃運行時起做用。
  • --continue參數運行時,可能會是A運行失敗後B運行了。

如以前所述,假設「應該在以後執行」的排序規則引入了排序循環。那麼它將會被忽略。

演示樣例 15.17. 當引入循環時,「應該在其以後執行」的任務排序會被忽略

build.gradle

task taskX << {
    println 'taskX'
}
task taskY << {
    println 'taskY'
}
task taskZ << {
    println 'taskZ'
}
taskX.dependsOn taskY
taskY.dependsOn taskZ
taskZ.shouldRunAfter taskX

gradle -q taskX的輸出結果

> gradle -q taskX
taskZ
taskY
taskX

15.6. 向任務加入描寫敘述

你可以向你的任務加入描寫敘述。好比,當運行gradle tasks時顯示這個描寫敘述。

演示樣例 15.18. 向任務加入描寫敘述

build.gradle

task copy(type: Copy) {
   description 'Copies the resource directory to the target directory.'
   from 'resources'
   into 'target'
   include('**/*.txt', '**/*.xml', '**/*.properties')
}

15.7. 替換任務

有時您想要替換一個任務。好比,您想要把經過 Java 插件加入的一個任務與不一樣類型的一個本身定義任務進行交換。你可以這樣實現:

演示樣例 15.19. 重寫任務

build.gradle

task copy(type: Copy)

task copy(overwrite: true) << {
    println('I am the new one.')
}

gradle -q copy 的輸出結果

> gradle -q copy
I am the new one.

在這裏咱們用一個簡單的任務替換Copy類型的任務。當建立這個簡單的任務時,您必須將overwrite屬性設置爲 true。不然 Gradle 將拋出異常,說這樣的名稱的任務已經存在。

15.8. 跳過任務

Gradle 提供多種方式來跳過任務的運行。

15.8.1. 使用斷言

你可以使用onlyIf()方法將斷言附加到一項任務中。

假設斷言結果爲 true,纔會運行任務的操做。

你可以用一個閉包來實現斷言。

閉包會做爲一個參數傳給任務。並且任務應該運行時返回true,或任務應該跳過期返回false。斷言僅僅在任務要運行前才計算。

演示樣例 15.20. 使用斷言跳過一個任務

build.gradle

task hello << {
    println 'hello world'
}

hello.onlyIf { !project.hasProperty('skipHello') }

gradle hello -PskipHello的輸出結果

> gradle hello -PskipHello
:hello SKIPPED

BUILD SUCCESSFUL

Total time: 1 secs

15.8.2. 使用 StopExecutionException

假設跳過任務的規則不能與斷言同一時候表達,您可以使用StopExecutionException。假設一個操做(action)拋出了此異常。那麼這個操做(action)接下來的行爲和這個任務的其它 操做(action)都會被跳過。構建會繼續運行下一個任務。

演示樣例 15.21. 使用 StopExecutionException 跳過任務

build.gradle

task compile << {
    println 'We are doing the compile.'
}

compile.doFirst {
    // Here you would put arbitrary conditions in real life. But we use this as an integration test, so we want defined behavior.
    if (true) { throw new StopExecutionException() }
}
task myTask(dependsOn: 'compile') << {
   println 'I am not affected'
}

gradle -q myTask 的輸出結果

> gradle -q myTask
I am not affected

假設您使用由 Gradle 提供的任務。那麼此功能將很實用。它贊成您向一個任務的內置操做中加入運行條件[7]

15.8.3. 啓用和禁用任務

每一項任務有一個默認值爲trueenabled標記。將它設置爲false,可以不讓這個任務的不論什麼操做運行。

演示樣例 15.22. 啓用和禁用任務

build.gradle

task disableMe << {
    println 'This should not be printed if the task is disabled.'
}
disableMe.enabled = false

Gradle disableMe的輸出結果

> gradle disableMe
:disableMe SKIPPED

BUILD SUCCESSFUL

Total time: 1 secs

15.9. 跳過處於最新狀態的任務

假設您使用 Gradle 自帶的任務。如 Java 插件所加入的任務的話,你可能已經注意到 Gradle 將跳過處於最新狀態的任務。

這樣的行在您自定義的任務上也有效。而不不過內置任務。

15.9.1. 聲明一個任務的輸入和輸出

讓咱們來看一個樣例。在這裏咱們的任務從一個 XML 源文件生成多個輸出文件。

讓咱們執行它幾回。

演示樣例 15.23. 一個生成任務

build.gradle

task transform {
    ext.srcFile = file('mountains.xml')
    ext.destDir = new File(buildDir, 'generated')
    doLast {
        println "Transforming source file."
        destDir.mkdirs()
        def mountains = new XmlParser().parse(srcFile)
        mountains.mountain.each { mountain ->
            def name = mountain.name[0].text()
            def height = mountain.height[0].text()
            def destFile = new File(destDir, "${name}.txt")
            destFile.text = "$name -> ${height}\n"
        }
    }
}

gradle transform的輸出結果

> gradle transform
:transform
Transforming source file.

gradle transform的輸出結果

> gradle transform
:transform
Transforming source file.

請注意 Gradle 第二次運行運行這項任務時,即便什麼都未做改變。也沒有跳過該任務。

咱們的演示樣例任務被用一個操做(action)閉包來定義。Gradle 不知道這個閉包作了什麼,也沒法本身主動推斷這個任務是否爲最新狀態。若要使用 Gradle 的最新狀態(up-to-date)檢查,您需要聲明這個任務的輸入和輸出。

每個任務都有一個inputsoutputs的屬性。用來聲明任務的輸入和輸出。

如下。咱們改動了咱們的演示樣例。聲明它將 XML 源文件做爲輸入,併產生輸出到一個目標文件夾。讓咱們執行它幾回。

演示樣例 15.24. 聲明一個任務的輸入和輸出

build.gradle

task transform {
    ext.srcFile = file('mountains.xml')
    ext.destDir = new File(buildDir, 'generated')
    inputs.file srcFile
    outputs.dir destDir
    doLast {
        println "Transforming source file."
        destDir.mkdirs()
        def mountains = new XmlParser().parse(srcFile)
        mountains.mountain.each { mountain ->
            def name = mountain.name[0].text()
            def height = mountain.height[0].text()
            def destFile = new File(destDir, "${name}.txt")
            destFile.text = "$name -> ${height}\n"
        }
    }
}

gradle transform的輸出結果

> gradle transform
:transform
Transforming source file.

gradle transform的輸出結果

> gradle transform
:transform UP-TO-DATE

現在,Gradle 知道哪些文件要檢查以肯定任務是否爲最新狀態。

任務的 inputs 屬性是 TaskInputs類型。任務的 outputs 屬性是 TaskOutputs類型。

一個未定義輸出的任務將永遠不會被看成是最新的。對於任務的輸出並不是文件的場景,或者是更復雜的場景。 TaskOutputs.upToDateWhen()方法贊成您以編程方式計算任務的輸出是否應該被推斷爲最新狀態。

一個僅僅定義了輸出的任務,假設自上一次構建以來它的輸出沒有改變。那麼它會被斷定爲最新狀態。

15.9.2. 它是怎麼實現的?

在第一次運行任務以前,Gradle 對輸入進行一次快照。這個快照包括了輸入文件集和每個文件的內容的哈希值。而後 Gradle 運行該任務。假設任務成功完畢,Gradle 將對輸出進行一次快照。

該快照包括輸出文件集和每個文件的內容的哈希值。Gradle 會保存這兩個快照。直到任務的下一次運行。

以後每一次。在運行任務以前,Gradle 會對輸入和輸出進行一次新的快照。假設新的快照和前一次的快照同樣。Gradle 會假定這些輸出是最新狀態的並跳過該任務。

假設它們不一則, Gradle 則會運行該任務。Gradle 會保存這兩個快照,直到任務的下一次運行。

請注意,假設一個任務有一個指定的輸出文件夾,在它上一次運行以後加入到該文件夾的所有文件都將被忽略,並且不會使這個任務成爲過期狀態。這是不相關的任務可以在不互相干擾的狀況下共用一個輸出文件夾。假設你因爲一些理由而不想這樣,請考慮使用TaskOutputs.upToDateWhen()

15.10. 任務規則

有時你想要有這樣一項任務。它的行爲依賴於參數數值範圍的一個大數或是無限的數字。任務規則是提供此類任務的一個很是好的表達方式:

演示樣例 15.25. 任務規則

build.gradle

tasks.addRule("Pattern: ping<ID>") { String taskName ->
    if (taskName.startsWith("ping")) {
        task(taskName) << {
            println "Pinging: " + (taskName - 'ping')
        }
    }
}

Gradle q pingServer1的輸出結果

> gradle -q pingServer1
Pinging: Server1

這個字符串參數被用做這條規則的描寫敘述。

當對這個樣例執行 gradle tasks 的時候,這個描寫敘述會被顯示。

規則不只僅是從命令行調用任務才起做用。

你也可以對基於規則的任務建立依賴關係:

演示樣例 15.26. 基於規則的任務依賴

build.gradle

tasks.addRule("Pattern: ping<ID>") { String taskName ->
    if (taskName.startsWith("ping")) {
        task(taskName) << {
            println "Pinging: " + (taskName - 'ping')
        }
    }
}

task groupPing {
    dependsOn pingServer1, pingServer2
}

Gradle q groupPing的輸出結果

> gradle -q groupPing
Pinging: Server1
Pinging: Server2

15.11. 析構器任務

析構器任務是一個 孵化中 的功能 (請參閱  C.1.2 章節, 「Incubating」)。

當終於的任務準備執行時,析構器任務會本身主動地加入到任務圖中。

演示樣例 15.27. 加入一個析構器任務

build.gradle

task taskX << {
    println 'taskX'
}
task taskY << {
    println 'taskY'
}

taskX.finalizedBy taskY

gradle -q taskX的輸出結果

> gradle -q taskX
taskX
taskY

即便終於的任務運行失敗,析構器任務也會被運行。

演示樣例 15.28. 運行失敗的任務的任務析構器

build.gradle

task taskX << {
    println 'taskX'
    throw new RuntimeException()
}
task taskY << {
    println 'taskY'
}

taskX.finalizedBy taskY

gradle -q taskX的輸出結果

> gradle -q taskX
taskX
taskY

還有一方面,假設終於的任務什麼都不作的話。比方由於失敗的任務依賴項或假設它被以爲是最新的狀態,析構任務不會運行。

在不管構建成功或是失敗。都必須清理建立的資源的狀況下,析構以爲是很是實用的。這種資源的一個樣例是。一個 web 容器會在集成測試任務前開始。並且在以後關閉。即便有些測試失敗。

你可以使用Task.finalizedBy()方法指定一個析構器任務。這種方法接受一個任務實例、 任務名稱或<a4><c5>Task.dependsOn()</c5></a4>所接受的不論什麼其它輸入做爲參數。

15.12. 總結

假設你是從 Ant 轉過來的。像Copy這樣的加強的 Gradle 任務。看起來就像是一個 Ant 目標(target)和一個 Ant 任務(task)之間的混合物。實際上確實是這樣子。Gradle 沒有像 Ant 那樣對任務和目標進行分離。簡單的 Gradle 任務就像 Ant 的目標,而加強的 Gradle 任務還包含 Ant 任務方面的內容。Gradle 的所有任務共享一個公共 API,您可以建立它們之間的依賴性。這種一個任務可能會比一個 Ant 任務更好配置。

它充分利用了類型系統,更具備表現力而且易於維護。



[7]你可能會想。爲何既不導入StopExecutionException也沒有經過其全然限定名來訪問它。

緣由是。Gradle 會向您的腳本加入默認的一些導入。這些導入是可本身定義的 (見附錄 E。現有的 IDE 支持和沒有支持時怎樣應對)。

相關文章
相關標籤/搜索