第15章 More about Tasks 更多關(guān)于任務(wù)

2018-02-24 15:56 更新

在入門教程中(Chapter 06. Build Script Basics 構(gòu)建腳本的基礎(chǔ)識),已經(jīng)學(xué)到了如何創(chuàng)建簡單 task。之后您還學(xué)習(xí)了如何將其他行為添加到這些 task 中,同時你已經(jīng)學(xué)會了如何創(chuàng)建 task 之間的依賴。這都是簡單的 task 。但 Gradle 讓 task 的概念更深遠(yuǎn)。Gradle 支持增強的task,也就是,有自己的屬性和方法的 task 。這是真正的與你所使用的 Ant target(目標(biāo))的不同之處。這種增強的任務(wù)可以由你提供,或由 Gradle 構(gòu)建。

15.1. Defining tasks 定義任務(wù)

在(Chapter 06. Build Script Basics 構(gòu)建腳本的基礎(chǔ)識)中我們已經(jīng)看到如何通過關(guān)鍵字這種風(fēng)格來定義 task 。在某些情況中,你可能需要使用這種關(guān)鍵字風(fēng)格的幾種不同的變式。例如,在表達(dá)式中不能用這種關(guān)鍵字風(fēng)格。

Example 15.1. Defining tasks

build.gradle

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

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

也可以使用字符串作為 task 名稱

Example 15.2. Defining tasks - using strings for task names

build.gradle

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

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

你可能更愿意使用另外一種替代的語法來定義任務(wù):

Example 15.3. Defining tasks with alternative syntax

build.gradle

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

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

15.2. Locating tasks 定位任務(wù)

你經(jīng)常需要在構(gòu)建文件中查找你所定義的 task,例如,為了去配置或是使用它們作為依賴。對這樣的情況,有很多種方法。首先,每個 task 都可作為 project 的一個屬性,并且使用 task 名稱作為這個屬性名稱:

Example 15.4. Accessing tasks as properties

build.gradle

task hello

println hello.name
println project.hello.name

task 也可以通過 task 集合來訪問

Example 15.5. Accessing tasks via tasks collection

build.gradle

task hello

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

您可以從任何 project 中,使用 tasks.getByPath() 方法獲取 task 路徑并且通過這個路徑來訪問 task。你可以用 task 名稱,相對路徑或者是絕對路徑作為參數(shù)調(diào)用 getByPath() 方法。

Example 15.6. Accessing tasks by path

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

執(zhí)行 gradle -q hello

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

詳見?TaskContainer?關(guān)于更多定位 task 的選項

15.3. Configuring tasks 配置任務(wù)

作為一個例子,讓我們看看由 Gradle 提供的 Copy task。若要創(chuàng)建Copy task ,您可以在構(gòu)建腳本中聲明:

Example 15.7. Creating a copy task

build.gradle

task myCopy(type: Copy)

上面的代碼創(chuàng)建了一個什么都沒做的復(fù)制 task ??梢允褂盟?API 來配置這個任務(wù) (見Copy)。下面的示例演示了幾種不同的方式來實現(xiàn)相同的配置。

要明白,意識到這項任務(wù)的名稱是 “myCopy”,但它的類型是“Copy”。你可以有多個同一類型的 task ,但具有不同的名稱。你會發(fā)現(xiàn)這給你一個很大的權(quán)力來實現(xiàn)橫切關(guān)注點在一個特定類型的所有 task。

Example 15.8. Configuring a task - various ways

build.gradle

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

這類似于我們通常在 Java 中配置對象的方式。您必須在每一次的配置語句重復(fù)上下文 (myCopy)。這顯得很冗余并且很不好讀。

還有另一種配置任務(wù)的方式。它也保留了上下文,且可以說是可讀性最強的。它是我們通常最喜歡的方式。

Example 15.9. Configuring a task - with closure

build.gradle

task myCopy(type: Copy)

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

這種方式適用于任何任務(wù)。該例子的第 3 行只是 tasks.getByName() 方法的簡潔寫法。特別要注意的是,如果您向 getByName() 方法傳入一個閉包,這個閉包的應(yīng)用是在配置這個任務(wù)的時候,而不是任務(wù)執(zhí)行的時候。

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

Example 15.10. Defining a task with closure

build.gradle

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

15.4. Adding dependencies to a task 給任務(wù)添加依賴

定義任務(wù)的依賴關(guān)系有幾種方法。在第 6.5 章節(jié),"任務(wù)依賴"中,已經(jīng)向你介紹了使用任務(wù)名稱來定義依賴。任務(wù)的名稱可以指向同一個項目中的任務(wù),或者其他項目中的任務(wù)。要引用另一個項目中的任務(wù),你需要把它所屬的項目的路徑作為前綴加到它的名字中。下面是一個示例,添加了從projectA:taskX 到 projectB:taskY 的依賴關(guān)系:

Example 15.11. Adding dependency on task from another project

build.gradle

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

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

執(zhí)行 gradle -q taskX

> gradle -q taskX
taskY
taskX

您可以使用一個 Task 對象而不是任務(wù)名稱來定義依賴,如下:

Example 15.12. Adding dependency using task object

build.gradle

task taskX << {
    println 'taskX'
}

task taskY << {
    println 'taskY'
}

taskX.dependsOn taskY

執(zhí)行 gradle -q taskX

> gradle -q taskX
taskY
taskX

對于更高級的用法,您可以使用閉包來定義 task 依賴。在計算依賴時,閉包會被傳入正在計算依賴的任務(wù)。這個閉包應(yīng)該返回一個 Task 對象或是Task 對象的集合,返回值會被作為這個 task 的依賴項。下面的示例是從taskX 加入了 project 中所有名稱以 lib 開頭的 task 的依賴

Example 15.13. Adding dependency using closure

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'
}

執(zhí)行 gradle -q taskX

> gradle -q taskX
lib1
lib2
taskX

更多關(guān)于 task 依賴 ,見?Task?API

15.5. Ordering tasks 排序任務(wù)

任務(wù)排序還是一個孵化中的功能。請注意此功能在以后的 Gradle 版本中可能會改變。

在某些情況下,控制兩個任務(wù)的執(zhí)行的順序,而不引入這些任務(wù)之間的顯式依賴,是很有用的。任務(wù)排序和任務(wù)依賴之間的主要區(qū)別是,排序規(guī)則不會影響那些任務(wù)的執(zhí)行,而僅將執(zhí)行的順序。

任務(wù)排序在許多情況下可能很有用:

  • 強制任務(wù)順序執(zhí)行: 如,'build' 永遠(yuǎn)不會在 'clean' 前面執(zhí)行。
  • 在構(gòu)建中盡早進(jìn)行構(gòu)建驗證:如,驗證在開始發(fā)布的工作前有一個正確的證書。
  • 通過在長久驗證前運行快速驗證以得到更快的反饋:如,單元測試應(yīng)在集成測試之前運行。
  • 一個任務(wù)聚合了某一特定類型的所有任務(wù)的結(jié)果:如,測試報告任務(wù)結(jié)合了所有執(zhí)行的測試任務(wù)的輸出。

有兩種排序規(guī)則是可用的:"必須在之后運行"和"應(yīng)該在之后運行"。

通過使用 “必須在之后運行”的排序規(guī)則,您可以指定 taskB 必須總是運行在 taskA 之后,無論 taskA 和 taskB 這兩個任務(wù)在什么時候被調(diào)度執(zhí)行。這被表示為 taskB.mustRunAfter(taskA) ?!皯?yīng)該在之后運行”的排序規(guī)則與其類似,但沒有那么嚴(yán)格,因為它在兩種情況下會被忽略。首先是如果使用這一規(guī)則引入了一個排序循環(huán)。其次,當(dāng)使用并行執(zhí)行,并且一個任務(wù)的所有依賴項除了任務(wù)應(yīng)該在之后運行之外所有條件已滿足,那么這個任務(wù)將會運行,不管它的“應(yīng)該在之后運行”的依賴項是否已經(jīng)運行了。當(dāng)傾向于更快的反饋時,會使用“應(yīng)該在之后運行”的規(guī)則,因為這種排序很有幫助但要求不嚴(yán)格。

目前使用這些規(guī)則仍有可能出現(xiàn) taskA 執(zhí)行而 taskB 沒有執(zhí)行,或者taskB 執(zhí)行而 taskA 沒有執(zhí)行。

Example 15.14. Adding a 'must run after' task ordering

build.gradle

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

執(zhí)行 gradle -q taskY taskX

> gradle -q taskY taskX
taskX
taskY

Example 15.15. Adding a 'should run after' task ordering

build.gradle

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

執(zhí)行 gradle -q taskY taskX

> gradle -q taskY taskX
taskX
taskY

在上面的例子中,它仍有可能執(zhí)行 taskY 而不會導(dǎo)致 taskX 也運行:

Example 15.16. Task ordering does not imply task execution

執(zhí)行 gradle -q taskY

> gradle -q taskY
taskY

如果想指定兩個任務(wù)之間的“必須在之后運行”和“應(yīng)該在之后運行”排序,可以使用?Task.mustRunAfter()?和Task.shouldRunAfter()?方法。這些方法接受一個任務(wù)實例、 任務(wù)名稱或?Task.dependsOn()?所接受的任何其他輸入作為參數(shù)。

請注意"B.mustRunAfter(A)"或"B.shouldRunAfter(A)"并不意味著這些任務(wù)之間的任何執(zhí)行上的依賴關(guān)系:

它是可以獨立地執(zhí)行任務(wù) A 和 B 的。

  • 排序規(guī)則僅在這兩項任務(wù)計劃執(zhí)行時起作用。
  • 當(dāng)--continue參數(shù)運行時,可能會是 A 執(zhí)行失敗后 B 執(zhí)行了。

如之前所述,如果“應(yīng)該在之后運行”的排序規(guī)則引入了排序循環(huán),那么它將會被忽略。

Example 15.17. A 'should run after' task ordering is ignored if it introduces an ordering cycle

build.gradle

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

執(zhí)行 gradle -q taskX

> gradle -q taskX
taskZ
taskY
taskX

15.6. Adding a description to a task 給任務(wù)添加描述

可以給任務(wù)添加描述,這個描述將會在 task 執(zhí)行時顯示。

Example 15.18. Adding a description to a task

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. Replacing tasks 替換任務(wù)

有時您想要替換一個任務(wù)。例如,您想要把通過 Java 插件添加的一個任務(wù)與不同類型的一個自定義任務(wù)進(jìn)行交換。你可以這樣實現(xiàn):

Example 15.19. Overwriting a task

build.gradle

task copy(type: Copy)

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

執(zhí)行 gradle -q copy

> gradle -q copy
I am the new one.

在這里我們用一個簡單的任務(wù)替換 Copy 類型的任務(wù)。當(dāng)創(chuàng)建這個簡單的任務(wù)時,您必須將 overwrite 屬性設(shè)置為 true。否則 Gradle 將拋出異常,說這種名稱的任務(wù)已經(jīng)存在。

15.8. Skipping tasks 跳過任務(wù)

Gradle 提供多種方式來跳過任務(wù)的執(zhí)行。

15.8.1. Using a predicate 使用斷言

你可以使用 onlyIf() 方法將斷言附加到一項任務(wù)中。如果斷言結(jié)果為 true,才會執(zhí)行任務(wù)的操作。你可以用一個閉包來實現(xiàn)斷言。閉包會作為一個參數(shù)傳給任務(wù),并且任務(wù)應(yīng)該執(zhí)行時返回 true,或任務(wù)應(yīng)該跳過時返回false。斷言只在任務(wù)要執(zhí)行前才計算。

Example 15.20. Skipping a task using a predicate

build.gradle

task hello << {
    println 'hello world'
}

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

執(zhí)行 gradle hello -PskipHello

> gradle hello -PskipHello
:hello SKIPPED

BUILD SUCCESSFUL

Total time: 1 secs

15.8.2. Using StopExecutionException

如果跳過任務(wù)的規(guī)則不能與斷言同時表達(dá),您可以使用StopExecutionException。如果一個操作(action)拋出了此異常,那么這個操作(action)接下來的行為和這個任務(wù)的其他 操作(action)都會被跳過。構(gòu)建會繼續(xù)執(zhí)行下一個任務(wù)。

Example 15.21. Skipping tasks with StopExecutionException

build.gradle

task compile << {
    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') << {
   println 'I am not affected'
}

Output of gradle -q myTask

> gradle -q myTask
I am not affected

如果您使用由 Gradle 提供的任務(wù),那么此功能將非常有用。它允許您向一個任務(wù)的內(nèi)置操作中添加執(zhí)行條件。(你可能會想,為什么既不導(dǎo)入StopExecutionException 也沒有通過其完全限定名來訪問它。原因是,Gradle 會向您的腳本添加默認(rèn)的一些導(dǎo)入。這些導(dǎo)入是可自定義的 (見Appendix E. Existing IDE Support and how to cope without it 支持的 IDE 以及如何應(yīng)對沒有它)。)

15.8.3. Enabling and disabling tasks 啟用和禁用任務(wù)

每一項任務(wù)有一個默認(rèn)值為 true 的 enabled 標(biāo)記。將它設(shè)置為 false,可以不讓這個任務(wù)的任何操作執(zhí)行。

Example 15.22. Enabling and disabling tasks

build.gradle

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

執(zhí)行 gradle disableMe

> gradle disableMe
:disableMe SKIPPED

BUILD SUCCESSFUL

Total time: 1 secs

15.9. Skipping tasks that are up-to-date 跳過處于最新狀態(tài)的任務(wù)

如果您使用 Gradle 自帶的任務(wù),如 Java 插件所添加的任務(wù)的話,你可能已經(jīng)注意到 Gradle 將跳過處于最新狀態(tài)的任務(wù)。這種行在您自己定義的任務(wù)上也有效,而不僅僅是內(nèi)置任務(wù)。

15.9.1. Declaring a task's inputs and outputs 聲明一個任務(wù)的輸入和輸出

讓我們來看一個例子。在這里我們的任務(wù)從一個 XML 源文件生成多個輸出文件。讓我們運行它幾次。

Example 15.23. A generator task

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"
        }
    }
}

執(zhí)行 gradle transform

> gradle transform
:transform
Transforming source file.

執(zhí)行 gradle transform

> gradle transform
:transform
Transforming source file.

請注意 Gradle 第二次執(zhí)行執(zhí)行這項任務(wù)時,即使什么都未作改變,也沒有跳過該任務(wù)。我們的示例任務(wù)被用一個操作(action)閉包來定義。Gradle 不知道這個閉包做了什么,也無法自動判斷這個任務(wù)是否為最新狀態(tài)。若要使用 Gradle 的最新狀態(tài)(up-to-date)檢查,您需要聲明這個任務(wù)的輸入和輸出。

每個任務(wù)都有一個 inputs 和 outputs 的屬性,用來聲明任務(wù)的輸入和輸出。下面,我們修改了我們的示例,聲明它將 XML 源文件作為輸入,并產(chǎn)生輸出到一個目標(biāo)目錄。讓我們運行它幾次。

Example 15.24. Declaring the inputs and outputs of a task

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"
        }
    }
}

執(zhí)行 gradle transform

> gradle transform
:transform
Transforming source file.

執(zhí)行 gradle transform

> gradle transform
:transform UP-TO-DATE

現(xiàn)在,Gradle 知道哪些文件要檢查以確定任務(wù)是否為最新狀態(tài)。

任務(wù)的 inputs 屬性是?TaskInputs類型。任務(wù)的 outputs 屬性是?TaskOutputs?類型。

一個沒有定義輸出的任務(wù)將永遠(yuǎn)不會被當(dāng)作是最新的。對于任務(wù)的輸出并不是文件的場景,或者是更復(fù)雜的場景,TaskOutputs.upToDateWhen()?方法允許您以編程方式計算任務(wù)的輸出是否應(yīng)該被判斷為最新狀態(tài)。

一個只定義了輸出的任務(wù),如果自上一次構(gòu)建以來它的輸出沒有改變,那么它會被判定為最新狀態(tài)。

15.9.2. How does it work 它是怎么實現(xiàn)的?

在第一次執(zhí)行任務(wù)之前,Gradle 對輸入進(jìn)行一次快照。這個快照包含了輸入文件集和每個文件的內(nèi)容的哈希值。然后 Gradle 執(zhí)行該任務(wù)。如果任務(wù)成功完成,Gradle 將對輸出進(jìn)行一次快照。該快照包含輸出文件集和每個文件的內(nèi)容的哈希值。Gradle 會保存這兩個快照,直到任務(wù)的下一次執(zhí)行。

之后每一次,在執(zhí)行任務(wù)之前,Gradle 會對輸入和輸出進(jìn)行一次新的快照。如果新的快照和前一次的快照一樣,Gradle 會假定這些輸出是最新狀態(tài)的并跳過該任務(wù)。如果它們不一則, Gradle 則會執(zhí)行該任務(wù)。Gradle 會保存這兩個快照,直到任務(wù)的下一次執(zhí)行。

請注意,如果一個任務(wù)有一個指定的輸出目錄,在它上一次執(zhí)行之后添加到該目錄的所有文件都將被忽略,并且不會使這個任務(wù)成為過時狀態(tài)。這是不相關(guān)的任務(wù)可以在不互相干擾的情況下共用一個輸出目錄。如果你因為一些理由而不想這樣,請考慮使用?TaskOutputs.upToDateWhen

15.10. Task rules 任務(wù)規(guī)則

有時你想要有這樣一項任務(wù),它的行為依賴于參數(shù)數(shù)值范圍的一個大數(shù)或是無限的數(shù)字。任務(wù)規(guī)則是提供此類任務(wù)的一個很好的表達(dá)方式:

Example 15.25. Task rule

build.gradle

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

執(zhí)行 gradle -q pingServer1

> gradle -q pingServer1
Pinging: Server1

這個字符串參數(shù)被用作這條規(guī)則的描述。當(dāng)對這個例子運行 gradle tasks 的時候,這個描述會被顯示。

規(guī)則不只是從命令行調(diào)用任務(wù)才起作用。你也可以對基于規(guī)則的任務(wù)創(chuàng)建依賴關(guān)系

Example 15.26. Dependency on rule based tasks

build.gradle

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

task groupPing {
    dependsOn pingServer1, pingServer2
}

執(zhí)行 gradle -q groupPing

> gradle -q groupPing
Pinging: Server1
Pinging: Server2

執(zhí)行 gradle -q tasks 你找不到 “pingServer1” 或 “pingServer2” 任務(wù),但腳本執(zhí)行邏輯是基于請求執(zhí)行這些任務(wù)的

15.11. Finalizer tasks 析構(gòu)器任務(wù)

析構(gòu)器任務(wù)是一個 孵化中 的功能 (請參閱Appendix C. The Feature Lifecycle 特性的生命周期?C.1.2 章節(jié), “Incubating”)。

當(dāng)最終的任務(wù)準(zhǔn)備運行時,析構(gòu)器任務(wù)會自動地添加到任務(wù)圖中。

Example 15.27. Adding a task finalizer

build.gradle

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

taskX.finalizedBy taskY

執(zhí)行 gradle -q taskX

> gradle -q taskX
taskX
taskY

即使最終的任務(wù)執(zhí)行失敗,析構(gòu)器任務(wù)也會被執(zhí)行。

Example 15.28. Task finalizer for a failing task

build.gradle

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

taskX.finalizedBy taskY

執(zhí)行 gradle -q taskX

> gradle -q taskX
taskX
taskY

另一方面,如果最終的任務(wù)什么都不做的話,比如由于失敗的任務(wù)依賴項或如果它被認(rèn)為是最新的狀態(tài),析構(gòu)任務(wù)不會執(zhí)行。

在不管構(gòu)建成功或是失敗,都必須清理創(chuàng)建的資源的情況下,析構(gòu)認(rèn)為是很有用的。這樣的資源的一個例子是,一個 web 容器會在集成測試任務(wù)前開始,并且在之后關(guān)閉,即使有些測試失敗。

你可以使用 Task.finalizedBy()?方法指定一個析構(gòu)器任務(wù)。這個方法接受一個任務(wù)實例、 任務(wù)名稱或Task.dependsOn()所接受的任何其他輸入作為參數(shù)。

15.12. Summary 總結(jié)

如果你是從 Ant 轉(zhuǎn)過來的,像 Copy 這種增強的 Gradle 任務(wù),看起來就像是一個 Ant target(目標(biāo))和一個 Ant task (任務(wù))之間的混合物。實際上確實是這樣子。Gradle 沒有像 Ant 那樣對任務(wù)和目標(biāo)進(jìn)行分離。簡單的 Gradle 任務(wù)就像 Ant 的目標(biāo),而增強的 Gradle 任務(wù)還包括 Ant 任務(wù)方面的內(nèi)容。Gradle 的所有任務(wù)共享一個公共 API,您可以創(chuàng)建它們之間的依賴性。這樣的一個任務(wù)可能會比一個 Ant 任務(wù)更好配置。它充分利用了類型系統(tǒng),更具有表現(xiàn)力而且易于維護(hù)。

以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號