有关其它已翻译的章节请关注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
在第 6 章。构建脚本基础 中咱们已经看到怎样经过keyword这样的风格来定义任务。编程
在某些状况中,你可能需要使用这样的keyword风格的几种不一样的变式。api
好比,在表达式中不能用这样的keyword风格。闭包
演示样例 15.1. 定义任务ide
build.gradle
post
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
。
你经常需要在构建文件里查找你所定义的任务。好比,为了去配置或是依赖它们。对这种状况。有很是多种方法。首先,每个任务均可做为项目的一个属性,并且使用任务名称做为这个属性名称:
任务也可以经过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
。
做为一个样例,让咱们看看由 Gradle 提供的Copy
任务。若要建立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()
方法传入一个闭包,这个闭包的应用是在配置这个任务的时候,而不是任务运行的时候。
您也可以在定义一个任务的时候使用一个配置闭包。
定义任务的依赖关系有几种方法。
在第 6.5 章节。"任务依赖"中。已经向你介绍了使用任务名称来定义依赖。任务的名称可以指向同一个项目中的任务,或者其它项目中的任务。要引用还有一个项目中的任务。你需要把它所属的项目的路径做为前缀加到它的名字中。如下是一个演示样例,加入了从projectA:taskX
到projectB: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。
任务排序仍是一个孵化中的功能。请注意此功能在之后的 Gradle 版本号中可能会改变。
在某些状况下。控制两个任务的运行的顺序,而不引入这些任务之间的显式依赖。是很是实用的。
任务排序和任务依赖之间的主要差异是,排序规则不会影响那些任务的运行,而仅将运行的顺序。
任务排序在不少状况下可能很是实用:
有两种排序规则是可用的:"必须在以后执行"和"应该在以后执行"。
经过使用 “ 必须在以后执行”的排序规则。您可以指定 taskB
必须老是执行在 taskA
以后。无论taskA
和taskB
这两个任务在何时被调度执行。这被表示为 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
也执行:
假设想指定两个任务之间的“必须在以后执行”和“应该在以后执行”排序。可以使用Task.mustRunAfter()
和Task.shouldRunAfter()
方法。
这些方法接受一个任务实例、 任务名称或Task.dependsOn()
所接受的不论什么其它输入做为參数。
请注意"B.mustRunAfter(A)
"或"B.shouldRunAfter(A)
"并不意味着这些任务之间的不论什么运行上的依赖关系:
A
和B
的。排序规则仅在这两项任务计划运行时起做用。--continue
參数运行时,可能会是A
运行失败后B
运行了。如以前所述,假设“应该在以后执行”的排序规则引入了排序循环。那么它将会被忽略。
你可以向你的任务加入描写叙述。好比,当运行gradle tasks
时显示这个描写叙述。
有时您想要替换一个任务。好比,您想要把经过 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 将抛出异常,说这样的名称的任务已经存在。
Gradle 提供多种方式来跳过任务的运行。
你可以使用onlyIf()
方法将断言附加到一项任务中。
假设断言结果为 true,才会运行任务的操做。
你可以用一个闭包来实现断言。
闭包会做为一个參数传给任务。并且任务应该运行时返回true,或任务应该跳过期返回false。断言仅仅在任务要运行前才计算。
假设跳过任务的规则不能与断言同一时候表达,您可以使用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]
每一项任务有一个默认值为true
的enabled
标记。将它设置为false
,可以不让这个任务的不论什么操做运行。
假设您使用 Gradle 自带的任务。如 Java 插件所加入的任务的话,你可能已经注意到 Gradle 将跳过处于最新状态的任务。
这样的行在您自定义的任务上也有效。而不不过内置任务。
让咱们来看一个样例。在这里咱们的任务从一个 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)检查,您需要声明这个任务的输入和输出。
每个任务都有一个inputs
和outputs
的属性。用来声明任务的输入和输出。
如下。咱们改动了咱们的演示样例。声明它将 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()
方法赞成您以编程方式计算任务的输出是否应该被推断为最新状态。
一个仅仅定义了输出的任务,假设自上一次构建以来它的输出没有改变。那么它会被断定为最新状态。
在第一次运行任务以前,Gradle 对输入进行一次快照。这个快照包括了输入文件集和每个文件的内容的哈希值。而后 Gradle 运行该任务。假设任务成功完毕,Gradle 将对输出进行一次快照。
该快照包括输出文件集和每个文件的内容的哈希值。Gradle 会保存这两个快照。直到任务的下一次运行。
以后每一次。在运行任务以前,Gradle 会对输入和输出进行一次新的快照。假设新的快照和前一次的快照同样。Gradle 会假定这些输出是最新状态的并跳过该任务。
假设它们不一则, Gradle 则会运行该任务。Gradle 会保存这两个快照,直到任务的下一次运行。
请注意,假设一个任务有一个指定的输出文件夹,在它上一次运行以后加入到该文件夹的所有文件都将被忽略,并且不会使这个任务成为过期状态。这是不相关的任务可以在不互相干扰的状况下共用一个输出文件夹。假设你因为一些理由而不想这样,请考虑使用TaskOutputs.upToDateWhen()
有时你想要有这样一项任务。它的行为依赖于參数数值范围的一个大数或是无限的数字。任务规则是提供此类任务的一个很是好的表达方式:
演示样例 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
析构器任务是一个 孵化中 的功能 (请參阅 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>所接受的不论什么其它输入做为參数。
假设你是从 Ant 转过来的。像Copy这样的加强的 Gradle 任务。看起来就像是一个 Ant 目标(target)和一个 Ant 任务(task)之间的混合物。实际上确实是这样子。Gradle 没有像 Ant 那样对任务和目标进行分离。简单的 Gradle 任务就像 Ant 的目标,而加强的 Gradle 任务还包含 Ant 任务方面的内容。Gradle 的所有任务共享一个公共 API,您可以建立它们之间的依赖性。这种一个任务可能会比一个 Ant 任务更好配置。
它充分利用了类型系统,更具备表现力而且易于维护。
[7]你可能会想。为何既不导入StopExecutionException
也没有经过其全然限定名来訪问它。
缘由是。Gradle 会向您的脚本加入默认的一些导入。这些导入是可本身定义的 (见附录 E。现有的 IDE 支持和没有支持时怎样应对)。