在入门教程构建基础中,你已经学习了如何创建简单的任务。之后您还学习了如何将其他行为添加到这些任务中。并且你已经学会了如何创建任务之间的依赖。这都是简单的任务。但 Gradle 让任务的概念更深远。Gradle 支持增强的任务,也就是,有自己的属性和方法的任务。这是真正的与你所使用的 Ant 目标(target)的不同之处。这种增强的任务可以由你提供,或由 Gradle 提供。
在构建基础中我们已经看到如何通过关键字这种风格来定义任务。在某些情况中,你可能需要使用这种关键字风格的几种不同的变式。例如,在表达式中不能用这种关键字风格。
定义任务
build.gradle
task(hello) << {
println "hello"
}
task(copy, type: Copy) {
from(file("srcDir"))
into(buildDir)
}
您还可以使用字符串作为任务名称:
定义任务 — — 使用字符串作为任务名称
build.gradle
task("hello") <<
{
println "hello"
}
task("copy", type: Copy) {
from(file("srcDir"))
into(buildDir)
}
对于定义任务,有一种替代的语法你可能更愿意使用:
使用替代语法定义任务
build.gradle
tasks.create(name: "hello") << {
println "hello"
}
tasks.create(name: "copy", type: Copy) {
from(file("srcDir"))
into(buildDir)
}
在这里我们将任务添加到 tasks 集合。关于 create() 方法的更多变化可以看看 TaskContainer。
你经常需要在构建文件中查找你所定义的任务,例如,为了去配置或是依赖它们。对这样的情况,有很多种方法。首先,每个任务都可作为项目的一个属性,并且使用任务名称作为这个属性名称:
以属性方式访问任务
build.gradle
task hello
println hello.name
println project.hello.name
任务也可以通过 tasks 集合来访问。
通过 tasks 集合访问任务
build.gradle
task hello
println tasks.hello.name
println tasks["hello"].name
您可以从任何项目中,使用 tasks.getByPath() 方法获取任务路径并且通过这个路径来访问任务。你可以用任务名称,相对路径或者是绝对路径作为参数调用 getByPath() 方法。
通过路径访问任务
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 任务,您可以在构建脚本中声明:
创建一个复制任务
build.gradle
task myCopy(type: Copy)
上面的代码创建了一个什么都没做的复制任务。可以使用它的 API 来配置这个任务(见 Copy)。下面的示例演示了几种不同的方式来实现相同的配置。
配置任务的几种方式
build.gradle
Copy myCopy = task(myCopy, type: Copy)
myCopy.from "resources"
myCopy.into "target"
myCopy.include("***.xml", "***.txt", "***.properties")
}
这种方式适用于任何任务。该例子的第 3 行只是 tasks.getByName() 方法的简洁写法。特别要注意的是,如果您向 getByName() 方法传入一个闭包,这个闭包的应用是在配置这个任务的时候,而不是任务执行的时候。
您也可以在定义一个任务的时候使用一个配置闭包。
使用闭包定义任务
build.gradle
task copy(type: Copy) {
from "resources"
into "target"
include("***.xml", "***.txt", "***.properties")
}
有时您想要替换一个任务。例如,您想要把通过 Java 插件添加的一个任务与不同类型的一个自定义任务进行交换。你可以这样实现:
重写任务
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。断言只在任务要执行前才计算。
使用断言跳过一个任务
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
如果跳过任务的规则不能与断言同时表达,您可以使用 StopExecutionException。如果一个操作(action)抛出了此异常,那么这个操作(action)接下来的行为和这个任务的其他 操作(action)都会被跳过。构建会继续执行下一个任务。
使用 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 提供的任务,那么此功能将非常有用。它允许您向一个任务的内置操作中添加执行条件。
每一项任务有一个默认值为 true 的 enabled 标记。将它设置为 false,可以不让这个任务的任何操作执行。
启用和禁用任务
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
如果您使用 Gradle 自带的任务,如 Java 插件所添加的任务的话,你可能已经注意到 Gradle 将跳过处于最新状态的任务。这种行在您自己定义的任务上也有效,而不仅仅是内置任务。
让我们来看一个例子。在这里我们的任务从一个 XML 源文件生成多个输出文件。让我们运行它几次。
一个生成任务
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 源文件作为输入,并产生输出到一个目标目录。让我们运行它几次。
声明一个任务的输入和输出
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()
有时你想要有这样一项任务,它的行为依赖于参数数值范围的一个大数或是无限的数字。任务规则是提供此类任务的一个很好的表达方式:
任务规则
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 的时候,这个描述会被显示。
规则不只是从命令行调用任务才起作用。你也可以对基于规则的任务创建依赖关系:
基于规则的任务依赖
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
析构器任务是一个孵化中的功能 。当最终的任务准备运行时,析构器任务会自动地添加到任务图中。
添加一个析构器任务
build.gradle
task taskX << {
println "taskX"
}
task taskY << {
println "taskY"
}
taskX.finalizedBy taskY
gradle -q taskX 的输出结果
> gradle -q taskX
taskX
taskY
即使最终的任务执行失败,析构器任务也会被执行。
执行失败的任务的任务析构器
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 任务更好配置。它充分利用了类型系统,更具有表现力而且易于维护。
如果你已经下载并安装了 Eclipse, 你只要再做一点点事情就可以开始了。Eclipse 附带预先绑定的 Ant 插件,随时可以使用。按照以...
SVN 生命周期本章讨论了版本控制系统的生命周期。在后面的章节中,我们将会介绍每个操作对应的 SVN 命令。创建版本库版本库相当...
本章节我们将为大家介绍 Git 的工作流程。一般工作流程如下:克隆 Git 资源作为工作目录。在克隆的资源上添加或修改文件。如果其...
上一章节中我们远程仓库使用了 Github,Github 公开的项目是免费的,但是如果你不想让其他人看到你的项目就需要收费。这时我们就...