Gradle 1.12 翻译——第十五章. 任务详述

有关其它已翻译的章节请关注Github上的项目:https://github.com/msdx/gradledoc/tree/1.12。或訪问:http://gradledoc.qiniudn.com/1.12/userguide/userguide.html

本文原创。转载请注明出处:http://blog.csdn.net/maosidiaoxian/article/details/41038305

关于我对Gradle的翻译。以Github上的项目及http://gradledoc.qiniudn.com 上的文档为准。

如有发现翻译有误的地方,将首先在以上两个地方更新。因时间精力问题,博客中发表的译文基本不会同步改动。


第十五章. 任务详述

在新手教程 (第 6 章。构建脚本基础) 中。你已经学习了怎样创建简单的任务。之后您还学习了怎样将其它行为加入到这些任务中。而且你已经学会了怎样创建任务之间的依赖。这都是简单的任务。但 Gradle 让任务的概念更深远。Gradle 支持增强的任务,也就是。有自己的属性和方法的任务。这是真正的与你所使用的 Ant 目标(target)的不同之处。这样的增强的任务能够由你提供,或由 Gradle 提供。

15.1. 定义任务

第 6 章。构建脚本基础 中我们已经看到怎样通过keyword这样的风格来定义任务。

在某些情况中,你可能须要使用这样的keyword风格的几种不同的变式。

比如,在表达式中不能用这样的keyword风格。

演示样例 15.1. 定义任务

build.gradle

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 支持和没有支持时怎样应对)。


posted on 2017-06-28 15:40  yjbjingcha  阅读(134)  评论(0编辑  收藏  举报

导航