第十五章. 任务详述

Chapter 15. More about Tasks

在入门教程 ( 第 6 章, 构建脚本基础) 中,你已经学习了如何创建简单的任务。之后您还学习了如何将其他行为添加到这些任务中。并且你已经学会了如何创建任务之间的依赖。这都是简单的任务。但 Gradle 让任务的概念更深远。Gradle 支持 增强的任务,也就是,有自己的属性和方法的任务。这是真正的与你所使用的 Ant 目标(target)的不同之处。这种增强的任务可以由你提供,或由 Gradle 提供。
In the introductory tutorial ( Chapter 6, Build Script Basics ) you have learned how to create simple tasks. You have also learned how to add additional behavior to these tasks later on. And you have learned how to create dependencies between tasks. This was all about simple tasks. But Gradle takes the concept of tasks further. Gradle supports enhanced tasks , that is, tasks which have their own properties and methods. This is really different to what you are used to with Ant targets. Such enhanced tasks are either provided by you or are provided by Gradle.

15.1. 定义任务

15.1. Defining tasks

第 6 章, 构建脚本基础 中我们已经看到如何通过关键字这种风格来定义任务。在某些情况中,你可能需要使用这种关键字风格的几种不同的变式。例如,在表达式中不能用这种关键字风格。
We have already seen how to define tasks using a keyword style in Chapter 6, Build Script Basics . There are a few variations on this style, which you may need to use in certain situations. For example, the keyword style does not work in expressions.

示例 15.1. 定义任务
Example 15.1. Defining tasks

build.gradle

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

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

您还可以使用字符串作为任务名称:
You can also use strings for the task names:

示例 15.2. 定义任务 — — 使用字符串作为任务名称
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)
}

对于定义任务,有一种替代的语法你可能更愿意使用:
There is an alternative syntax for defining tasks, which you may prefer to use:

示例 15.3. 使用替代语法定义任务
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)
}

在这里我们将任务添加到 tasks集合。关于 create ()方法的更多变化可以看看 TaskContainer
Here we add tasks to the tasks collection. Have a look at TaskContainer for more variations of the create() method.

15.2. 定位任务

15.2. Locating tasks

你经常需要在构建文件中查找你所定义的任务,例如,为了去配置或是依赖它们。对这样的情况,有很多种方法。首先,每个任务都可作为项目的一个属性,并且使用任务名称作为这个属性名称:
You often need to locate the tasks that you have defined in the build file, for example, to configure them or use them for dependencies. There are a number of ways of doing this. Firstly, each task is available as a property of the project, using the task name as the property name:

示例 15.4. 以属性方式访问任务
Example 15.4. Accessing tasks as properties

build.gradle

task hello

println hello.name
println project.hello.name

任务也可以通过 tasks集合来访问。
Tasks are also available through the tasks collection.

示例 15.5. 通过tasks集合访问任务
Example 15.5. Accessing tasks via tasks collection

build.gradle

task hello

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

您可以从任何项目中,使用 tasks.getByPath()方法获取任务路径并且通过这个路径来访问任务。你可以用任务名称,相对路径或者是绝对路径作为参数调用 getByPath()方法。
You can access tasks from any project using the task's path using the tasks.getByPath() method. You can call the getByPath() method with a task name, or a relative path, or an absolute path.

示例15.6. 通过路径访问任务
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

gradle -q hello 的输出结果
Output of gradle -q hello

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

有关查找任务的更多选项,可以看一下 TaskContainer
Have a look at TaskContainer for more options for locating tasks.

15.3. 配置任务

15.3. Configuring tasks

作为一个例子,让我们看看由 Gradle 提供的 Copy任务。若要创建 Copy任务,您可以在构建脚本中声明:
As an example, let's look at the Copy task provided by Gradle. To create a Copy task for your build, you can declare in your build script:

示例 15.7. 创建一个复制任务
Example 15.7. Creating a copy task

build.gradle

task myCopy(type: Copy)

上面的代码创建了一个什么都没做的复制任务。可以使用它的 API 来配置这个任务 (见 Copy )。下面的示例演示了几种不同的方式来实现相同的配置。
This creates a copy task with no default behavior. The task can be configured using its API (see Copy ). The following examples show several different ways to achieve the same configuration.

示例 15.8. 配置任务的几种方式
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 中配置对象的方式。您必须在每一次的配置语句重复上下文 ( myCopy)。这显得很冗余并且很不好读。
This is similar to the way we would normally configure objects in Java. You have to repeat the context ( myCopy ) in the configuration statement every time. This is a redundancy and not very nice to read.

还有另一种配置任务的方式。它也保留了上下文,且可以说是可读性最强的。它是我们通常最喜欢的方式。
There is another way of configuring a task. It also preserves the context and it is arguably the most readable. It is usually our favorite.

示例 15.9. 配置任务-使用闭包
Example 15.9. Configuring a task - with closure

build.gradle

task myCopy(type: Copy)

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

这种方式适用于 任何 任务。该例子的第 3 行只是 tasks.getByName()方法的简洁写法。特别要注意的是,如果您向 getByName()方法传入一个闭包,这个闭包的应用是在 配置 这个任务的时候,而不是任务执行的时候。
This works for any task. Line 3 of the example is just a shortcut for the tasks.getByName() method. It is important to note that if you pass a closure to the getByName() method, this closure is applied to configure the task, not when the task executes.

您也可以在定义一个任务的时候使用一个配置闭包。
You can also use a configuration closure when you define a task.

示例 15.10. 使用闭包定义任务
Example 15.10. Defining a task with closure

build.gradle

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

15.4. 对任务添加依赖

15.4. Adding dependencies to a task

定义任务的依赖关系有几种方法。在 第 6.5 章节,"任务依赖"中,已经向你介绍了使用任务名称来定义依赖。任务的名称可以指向同一个项目中的任务,或者其他项目中的任务。要引用另一个项目中的任务,你需要把它所属的项目的路径作为前缀加到它的名字中。下面是一个示例,添加了从 projectA:taskXprojectB:taskY的依赖关系:
There are several ways you can define the dependencies of a task. In Section 6.5, “Task dependencies” you were introduced to defining dependencies using task names. Task names can refer to tasks in the same project as the task, or to tasks in other projects. To refer to a task in another project, you prefix the name of the task with the path of the project it belongs to. Below is an example which adds a dependency from projectA:taskX to projectB:taskY :

示例 15.11. 从另一个项目的任务上添加依赖
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'
    }
}

gradle -q taskX 的输出结果
Output of gradle -q taskX

> gradle -q taskX
taskY
taskX

您可以使用一个 Task对象而不是任务名称来定义依赖,如下:
Instead of using a task name, you can define a dependency using a Task object, as shown in this example:

示例 15.12. 使用 task 对象添加依赖
Example 15.12. Adding dependency using task object

build.gradle

task taskX << {
    println 'taskX'
}

task taskY << {
    println 'taskY'
}

taskX.dependsOn taskY

gradle -q taskX 的输出结果
Output of gradle -q taskX

> gradle -q taskX
taskY
taskX

对于更高级的用法,您可以使用闭包来定义任务依赖。在计算依赖时,闭包会被传入正在计算依赖的任务。这个闭包应该返回一个Task 对象或是 Task 对象的集合,返回值会被作为这个任务的依赖项。下面的示例是从 taskX加入了项目中所有名称以 lib开头的任务的依赖:
For more advanced uses, you can define a task dependency using a closure. When evaluated, the closure is passed the task whose dependencies are being calculated. The closure should return a single Task or collection of Task objects, which are then treated as dependencies of the task. The following example adds a dependency from taskX to all the tasks in the project whose name starts with lib :

示例 15.13. 使用闭包添加依赖
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'
}

gradle -q taskX 的输出结果
Output of gradle -q taskX

> gradle -q taskX
lib1
lib2
taskX

有关任务依赖的详细信息,请参阅 Task 的 API。
For more information about task dependencies, see the Task API.

15.5. 任务排序

15.5. Ordering tasks

任务排序还是一个 孵化中的功能。请注意此功能在以后的 Gradle 版本中可能会改变。
Task ordering is an incubating feature. Please be aware that this feature may change in later Gradle versions.

在某些情况下,控制两个任务的执行的 顺序 ,而不引入这些任务之间的显式依赖,是很有用的。任务 排序 和任务 依赖 之间的主要区别是,排序规则不会影响那些任务的执行,而仅将执行的顺序。
In some cases it is useful to control the order in which 2 tasks will execute, without introducing an explicit dependency between those tasks. The primary difference between a task ordering and a task dependency is that an ordering rule does not influence which tasks will be executed, only the order in which they will be executed.

任务排序在许多情况下可能很有用:
Task ordering can be useful in a number of scenarios:

  • 强制任务顺序执行: 如“build”永远不会在 "clean" 前面执行。
    Enforce sequential ordering of tasks: eg. 'build' never runs before 'clean'.
  • 在构建中尽早进行构建验证:如,验证在开始发布的工作前有一个正确的证书。
    Run build validations early in the build: eg. validate I have the correct credentials before starting the work for a release build.
  • 通过在长久验证前运行快速验证以得到更快的反馈:如,单元测试应在集成测试之前运行。
    Get feedback faster by running quick verification tasks before long verification tasks: eg. unit tests should run before integration tests.
  • 一个任务聚合了某一特定类型的所有任务的结果:如,测试报告任务结合了所有执行的测试任务的输出。
    A task that aggregates the results of all tasks of a particular type: eg. test report task combines the outputs of all executed test tasks.

有两种排序规则是可用的:“ 必须在之后运行 ”和“ 应该在之后运行 ”。
There are two ordering rules available: " must run after " and " should run after ".

通过使用 “ 必须在之后运行”的排序规则,您可以指定 taskB 必须总是运行在 taskA 之后,无论 taskAtaskB这两个任务在什么时候被调度执行。这被表示为 taskB.mustRunAfter(taskA) 。“应该在之后运行”的排序规则与其类似,但没有那么严格,因为它在两种情况下会被忽略。首先是如果使用这一规则引入了一个排序循环。其次,当使用并行执行,并且一个任务的所有依赖项除了任务应该在之后运行之外所有条件已满足,那么这个任务将会运行,不管它的“应该在之后运行”的依赖项是否已经运行了。当倾向于更快的反馈时,会使用“应该在之后运行”的规则,因为这种排序很有帮助但要求不严格。
By using 'must run after" ordering rule you can specify that taskB must always run after taskA , whenever both taskA and taskB are scheduled for execution. This is expressed as taskB.mustRunAfter(taskA) . The 'should run after' ordering rule is similar but less strict as it will be ignored in two situations. Firstly if using that rule introduces an ordering cycle. Secondly when using parallel execution and all dependencies of a task have been satisfied apart from should run after then this task will be run regardless of weather its 'should run after' dependencies have been run or not. You would use 'should run after' rule when ordering preference for faster feedback, where the ordering is helpful but not strictly required.

目前使用这些规则仍有可能出现 taskA执行而 taskB 没有执行,或者 taskB执行而 taskA 没有执行。
With these rules present it is still possible to execute taskA without taskB and vice-versa.

示例 15.14. 添加“必须在之后运行”的任务排序
Example 15.14. Adding a 'must run after' task ordering

build.gradle

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

gradle -q taskY taskX 的输出结果
Output of gradle -q taskY taskX

> gradle -q taskY taskX
taskX
taskY

示例 15.15. 添加“应该在之后运行”的任务排序
Example 15.15. Adding a 'should run after' task ordering

build.gradle

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

gradle -q taskY taskX 的输出结果
Output of gradle -q taskY taskX

> gradle -q taskY taskX
taskX
taskY

在上面的例子中,它仍有可能执行 taskY而不会导致 taskX也运行:
In the examples above, it is still possible to execute taskY without causing taskX to run:

示例 15.16. 任务排序并不意味着任务执行
Example 15.16. Task ordering does not imply task execution

gradle -q taskY 的输出结果
Output of gradle -q taskY

> gradle -q taskY
taskY

如果想指定两个任务之间的“必须在之后运行”和“应该在之后运行”排序,可以使用 Task.mustRunAfter() Task.shouldRunAfter() 方法。这些方法接受一个任务实例、 任务名称或 Task.dependsOn() 所接受的任何其他输入作为参数。
To specify a "must run after" or "should run after" ordering between 2 tasks, you use the Task.mustRunAfter() and Task.shouldRunAfter() methods. These method accept a task instance, a task name or any other input accepted by Task.dependsOn() .

请注意“ B.mustRunAfter(A)”或“ B.shouldRunAfter(A)”并不意味着这些任务之间的任何执行上的依赖关系:
Note that " B.mustRunAfter(A) " or " B.shouldRunAfter(A) " does not imply any execution dependency between the tasks:

  • 它是可以独立地执行任务 AB 的。排序规则仅在这两项任务计划执行时起作用。
    It is possible to execute tasks A and B independently. The ordering rule only has an effect when both tasks are scheduled for execution.
  • --continue参数运行时,可能会是 A执行失败后 B执行了。
    When run with --continue , it is possible for B to execute in the event that A fails.

如之前所述,如果“应该在之后运行”的排序规则引入了排序循环,那么它将会被忽略。
As mentioned before 'should run after' ordering rule will be ignored if it introduces an ordering cycle:

示例 15.17. 当引入循环时,“应该在其之后运行”的任务排序会被忽略
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

gradle -q taskX 的输出结果
Output of gradle -q taskX

> gradle -q taskX
taskZ
taskY
taskX

15.6. 向任务添加描述

15.6. Adding a description to a task

你可以向你的任务添加描述。例如,当执行 gradle tasks 时显示这个描述。
You can add a description to your task. This description is for example displayed when executing gradle tasks .

示例 15.18. 向任务添加描述
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. 替换任务

15.7. Replacing tasks

有时您想要替换一个任务。例如,您想要把通过 Java 插件添加的一个任务与不同类型的一个自定义任务进行交换。你可以这样实现:
Sometimes you want to replace a task. For example if you want to exchange a task added by the Java plugin with a custom task of a different type. You can achieve this with:

示例 15.19. 重写任务
Example 15.19. Overwriting a task

build.gradle

task copy(type: Copy)

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

gradle -q copy 的输出结果
Output of gradle -q copy

> gradle -q copy
I am the new one.

在这里我们用一个简单的任务替换 Copy类型的任务。当创建这个简单的任务时,您必须将 overwrite属性设置为 true。否则 Gradle 将抛出异常,说这种名称的任务已经存在。
Here we replace a task of type Copy with a simple task. When creating the simple task, you have to set the overwrite property to true. Otherwise Gradle throws an exception, saying that a task with such a name already exists.

15.8. 跳过任务

15.8. Skipping tasks

Gradle 提供多种方式来跳过任务的执行。
Gradle offers multiple ways to skip the execution of a task.

15.8.1. 使用断言

15.8.1. Using a predicate

你可以使用 onlyIf()方法将断言附加到一项任务中。如果断言结果为 true,才会执行任务的操作。你可以用一个闭包来实现断言。闭包会作为一个参数传给任务,并且任务应该执行时返回true,或任务应该跳过时返回false。断言只在任务要执行前才计算。
You can use the onlyIf() method to attach a predicate to a task. The task's actions are only executed if the predicate evaluates to true. You implement the predicate as a closure. The closure is passed the task as a parameter, and should return true if the task should execute and false if the task should be skipped. The predicate is evaluated just before the task is due to be executed.

示例 15.20. 使用断言跳过一个任务
Example 15.20. Skipping a task using a predicate

build.gradle

task hello << {
    println 'hello world'
}

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

gradle hello -PskipHello 的输出结果
Output of gradle hello -PskipHello

> gradle hello -PskipHello
:hello SKIPPED

BUILD SUCCESSFUL

Total time: 1 secs

15.8.2. 使用 StopExecutionException

15.8.2. Using StopExecutionException

如果跳过任务的规则不能与断言同时表达,您可以使用 StopExecutionException 。如果一个操作(action)抛出了此异常,那么这个操作(action)接下来的行为和这个任务的其他 操作(action)都会被跳过。构建会继续执行下一个任务。
If the rules for skipping a task can't be expressed with predicate, you can use the StopExecutionException . If this exception is thrown by an action, the further execution of this action as well as the execution of any following action of this task is skipped. The build continues with executing the next task.

示例 15.21. 使用 StopExecutionException 跳过任务
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 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 的输出结果
Output of gradle -q myTask

> gradle -q myTask
I am not affected

如果您使用由 Gradle 提供的任务,那么此功能将非常有用。它允许您向一个任务的内置操作中添加执行 条件 [ 7]
This feature is helpful if you work with tasks provided by Gradle. It allows you to add conditional execution of the built-in actions of such a task. [ 7 ]

15.8.3. 启用和禁用任务

15.8.3. Enabling and disabling tasks

每一项任务有一个默认值为 trueenabled标记。将它设置为 false,可以不让这个任务的任何操作执行。
Every task has also an enabled flag which defaults to true . Setting it to false prevents the execution of any of the task's actions.

示例 15.22. 启用和禁用任务
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

Gradle disableMe 的输出结果
Output of gradle disableMe

> gradle disableMe
:disableMe SKIPPED

BUILD SUCCESSFUL

Total time: 1 secs

15.9. 跳过处于最新状态的任务

15.9. Skipping tasks that are up-to-date

如果您使用 Gradle 自带的任务,如 Java 插件所添加的任务的话,你可能已经注意到 Gradle 将跳过处于最新状态的任务。这种行在您自己定义的任务上也有效,而不仅仅是内置任务。
If you are using one of the tasks that come with Gradle, such as a task added by the Java plugin, you might have noticed that Gradle will skip tasks that are up-to-date. This behaviour is also available for your tasks, not just for built-in tasks.

15.9.1. 声明一个任务的输入和输出

15.9.1. Declaring a task's inputs and outputs

让我们来看一个例子。在这里我们的任务从一个 XML 源文件生成多个输出文件。让我们运行它几次。
Let's have a look at an example. Here our task generates several output files from a source XML file. Let's run it a couple of times.

示例 15.23. 一个生成任务
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"
        }
    }
}

gradle transform 的输出结果
Output of gradle transform

> gradle transform
:transform
Transforming source file.

gradle transform 的输出结果
Output of gradle transform

> gradle transform
:transform
Transforming source file.

请注意 Gradle 第二次执行执行这项任务时,即使什么都未作改变,也没有跳过该任务。我们的示例任务被用一个操作(action)闭包来定义。Gradle 不知道这个闭包做了什么,也无法自动判断这个任务是否为最新状态。若要使用 Gradle 的最新状态(up-to-date)检查,您需要声明这个任务的输入和输出。
Notice that Gradle executes this task a second time, and does not skip the task even though nothing has changed. Our example task was defined using an action closure. Gradle has no idea what the closure does and cannot automatically figure out whether the task is up-to-date or not. To use Gradle's up-to-date checking, you need to declare the inputs and outputs of the task.

每个任务都有一个 inputsoutputs的属性,用来声明任务的输入和输出。下面,我们修改了我们的示例,声明它将 XML 源文件作为输入,并产生输出到一个目标目录。让我们运行它几次。
Each task has an inputs and outputs property, which you use to declare the inputs and outputs of the task. Below, we have changed our example to declare that it takes the source XML file as an input and produces output to a destination directory. Let's run it a couple of times.

示例 15.24. 声明一个任务的输入和输出
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"
        }
    }
}

gradle transform 的输出结果
Output of gradle transform

> gradle transform
:transform
Transforming source file.

gradle transform 的输出结果
Output of gradle transform

> gradle transform
:transform UP-TO-DATE

现在,Gradle 知道哪些文件要检查以确定任务是否为最新状态。
Now, Gradle knows which files to check to determine whether the task is up-to-date or not.

任务的 inputs 属性是 TaskInputs 类型。任务的 outputs 属性是 TaskOutputs 类型。
The task's inputs property is of type TaskInputs . The task's outputs property is of type TaskOutputs .

一个没有定义输出的任务将 永远不会 被当作是最新的。对于任务的输出并不是文件的场景,或者是更复杂的场景, TaskOutputs.upToDateWhen() 方法允许您以编程方式计算任务的输出是否应该被判断为最新状态。
A task with no defined outputs will never be considered up-to-date. For scenarios where the outputs of a task are not files, or for more complex scenarios, the TaskOutputs.upToDateWhen() method allows you to calculate programmatically if the tasks outputs should be considered up to date.

一个只定义了输出的任务,如果自上一次构建以来它的输出没有改变,那么它会被判定为最新状态。
A task with only outputs defined will be considered up-to-date if those outputs are unchanged since the previous build.

15.9.2. 它是怎么实现的?

15.9.2. How does it work?

在第一次执行任务之前,Gradle 对输入进行一次快照。这个快照包含了输入文件集和每个文件的内容的哈希值。然后 Gradle 执行该任务。如果任务成功完成,Gradle 将对输出进行一次快照。该快照包含输出文件集和每个文件的内容的哈希值。Gradle 会保存这两个快照,直到任务的下一次执行。
Before a task is executed for the first time, Gradle takes a snapshot of the inputs. This snapshot contains the set of input files and a hash of the contents of each file. Gradle then executes the task. If the task completes successfully, Gradle takes a snapshot of the outputs. This snapshot contains the set of output files and a hash of the contents of each file. Gradle persists both snapshots for next time the task is executed.

之后每一次,在执行任务之前,Gradle 会对输入和输出进行一次新的快照。如果新的快照和前一次的快照一样,Gradle 会假定这些输出是最新状态的并跳过该任务。如果它们不一则, Gradle 则会执行该任务。Gradle 会保存这两个快照,直到任务的下一次执行。
Each time after that, before the task is executed, Gradle takes a new snapshot of the inputs and outputs. If the new snapshots are the same as the previous snapshots, Gradle assumes that the outputs are up to date and skips the task. If they are not the same, Gradle executes the task. Gradle persists both snapshots for next time the task is executed.

请注意,如果一个任务有一个指定的输出目录,在它上一次执行之后添加到该目录的所有文件都将被忽略,并且不会使这个任务成为过时状态。这是不相关的任务可以在不互相干扰的情况下共用一个输出目录。如果你因为一些理由而不想这样,请考虑使用 TaskOutputs.upToDateWhen()
Note that if a task has an output directory specified, any files added to that directory since the last time it was executed are ignored and will NOT cause the task to be out of date. This is so unrelated tasks may share an output directory without interfering with each other. If this is not the behaviour you want for some reason, consider using TaskOutputs.upToDateWhen()

15.10. 任务规则

15.10. Task rules

有时你想要有这样一项任务,它的行为依赖于参数数值范围的一个大数或是无限的数字。任务规则是提供此类任务的一个很好的表达方式:
Sometimes you want to have a task whose behavior depends on a large or infinite number value range of parameters. A very nice and expressive way to provide such tasks are task rules:

示例 15.25. 任务规则
Example 15.25. Task rule

build.gradle

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

Gradle q pingServer1 的输出结果
Output of gradle -q pingServer1

> gradle -q pingServer1
Pinging: Server1

这个字符串参数被用作这条规则的描述。当对这个例子运行 gradle tasks 的时候,这个描述会被显示。
The String parameter is used as a description for the rule. This description is shown when running for example gradle tasks .

规则不只是从命令行调用任务才起作用。你也可以对基于规则的任务创建依赖关系:
Rules not just work when calling tasks from the command line. You can also create dependsOn relations on rule based tasks:

示例 15.26. 基于规则的任务依赖
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
}

Gradle q groupPing 的输出结果
Output of gradle -q groupPing

> gradle -q groupPing
Pinging: Server1
Pinging: Server2

15.11. 析构器任务

15.11. Finalizer tasks

析构器任务是一个 孵化中 的功能 (请参阅 C.1.2 章节, “Incubating”)。
Finalizers tasks are an incubating feature (see Section C.1.2, “Incubating” ).

当最终的任务准备运行时,析构器任务会自动地添加到任务图中。
Finalizer tasks are automatically added to the task graph when the finalized task is scheduled to run.

示例 15.27. 添加一个析构器任务
Example 15.27. Adding a task finalizer

build.gradle

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

taskX.finalizedBy taskY

gradle -q taskX 的输出结果
Output of gradle -q taskX

> gradle -q taskX
taskX
taskY

即使最终的任务执行失败,析构器任务也会被执行。
Finalizer task will be executed even if the finalized task fails.

示例 15.28. 执行失败的任务的任务析构器
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

gradle -q taskX 的输出结果
Output of gradle -q taskX

> gradle -q taskX
taskX
taskY

另一方面,如果最终的任务什么都不做的话,比如由于失败的任务依赖项或如果它被认为是最新的状态,析构任务不会执行。
On the other hand, finalizer tasks are not executed if the finalized task didn't do any work, for example due to failed task dependency or if it's considered up to date.

在不管构建成功或是失败,都必须清理创建的资源的情况下,析构认为是很有用的。这样的资源的一个例子是,一个 web 容器会在集成测试任务前开始,并且在之后关闭,即使有些测试失败。
Finalizer tasks are useful in situations where build creates a resource that has to be cleaned up regardless of the build failing or succeeding. An example of such resource is a web container started before an integration test task and which should be always shut down, even if some of the tests fail.

你可以使用 Task.finalizedBy() 方法指定一个析构器任务。这个方法接受一个任务实例、 任务名称或 Task.dependsOn()所接受的任何其他输入作为参数。 To specify a finalizer task you use the Task.finalizedBy() method. This method accepts a task instance, a task name or any other input accepted by Task.dependsOn() .

15.12. 总结

15.12. Summary

如果你是从 Ant 转过来的,像 Copy 这种增强的 Gradle 任务,看起来就像是一个 Ant 目标(target)和一个 Ant 任务(task)之间的混合物。实际上确实是这样子。Gradle 没有像 Ant 那样对任务和目标进行分离。简单的 Gradle 任务就像 Ant 的目标,而增强的 Gradle 任务还包括 Ant 任务方面的内容。Gradle 的所有任务共享一个公共 API,您可以创建它们之间的依赖性。这样的一个任务可能会比一个 Ant 任务更好配置。它充分利用了类型系统,更具有表现力而且易于维护。
If you are coming from Ant, such an enhanced Gradle task as Copy looks like a mixture between an Ant target and an Ant task. And this is actually the case. The separation that Ant does between tasks and targets is not done by Gradle. The simple Gradle tasks are like Ant's targets and the enhanced Gradle tasks also include the Ant task aspects. All of Gradle's tasks share a common API and you can create dependencies between them. Such a task might be nicer to configure than an Ant task. It makes full use of the type system, is more expressive and easier to maintain.



[ 7]你可能会想,为什么既不导入 StopExecutionException也没有通过其完全限定名来访问它。原因是,Gradle 会向您的脚本添加默认的一些导入。这些导入是可自定义的 (见 附录 E, 现有的 IDE 支持和没有支持时如何应对)。
[ 7 ] You might be wondering why there is neither an import for the StopExecutionException nor do we access it via its fully qualified name. The reason is, that Gradle adds a set of default imports to your script. These imports are customizable (see Appendix E, Existing IDE Support and how to cope without it ).