简介
在之前的文章中,我们讲到了如何使用 gradle 创建一个简单的 task,以及 task 之间怎么依赖,甚至使用了程序来创建 task。在本文中,我们会更加深入的去了解一下 gradle 中的 task。
定义 task
定义一个 task 可以有很多种方式,比如下面的使用 string 作为 task 的名字:
task('hello') {
doLast {
println "hello"
}
}
task('copy', type: Copy) {
from(file('srcDir'))
into(buildDir)
}
复制代码
还可以使用 tasks 容器来创建:
tasks.create('hello') {
doLast {
println "hello"
}
}
tasks.create('copy', Copy) {
from(file('srcDir'))
into(buildDir)
}
复制代码
上面的例子中,我们使用 tasks.create 方法,将新创建的 task 加到 tasks 集合中。
我们还可以使用 groovy 特有的语法来定义一个 task:
task(hello) {
doLast {
println "hello"
}
}
task(copy, type: Copy) {
from(file('srcDir'))
into(buildDir)
}
复制代码
tasks 集合类
上面我们在创建 task 的时候,使用了 tasks 集合类来创建 task。
实际上,tasks 集合类是一个非常有用的工具类,我们可以使用它来做很多事情。
直接在 build 文件中使用 tasks,实际上是引用了 TaskContainer 的一个实例对象。我们还可以使用 Project.getTasks()
来获取这个实例对象。
我们看下 TaskContainer 的定义:
public interface TaskContainer extends TaskCollection<Task>, PolymorphicDomainObjectContainer<Task>
复制代码
从定义上,我们可以看出 TaskContainer 是一个 task 的集合和域对象的集合。
taskContainer 中有四类非常重要的方法:
第一类是定位 task 的方法,有个分别是 findByPath 和 getByPath。两个方法的区别就是 findByPath 如果没找到会返回 null,而 getByPath 没找到的话会抛出 UnknownTaskException。
看下怎么使用:
task hello
println tasks.getByPath('hello').path
println tasks.getByPath(':hello').path
复制代码
输出:
第二类是创建 task 的方法 create,create 方法有多种实现,你可以直接通过名字来创建一个 task:
task('hello') {
doLast {
println "hello"
}
}
复制代码
也可以创建特定类型的 task:
task('copy', type: Copy) {
from(file('srcDir'))
into(buildDir)
}
复制代码
还可以创建带参数的构造函数的 task:
class CustomTask extends DefaultTask {
final String message
final int number
@Inject
CustomTask(String message, int number) {
this.message = message
this.number = number
}
}
复制代码
上面我们为 CustomTask 创建了一个带参数的构造函数,注意,这里需要带上 @javax.inject.Inject 注解,表示我们后面可以传递参数给这个构造函数。
我们可以这样使用:
tasks.create('myTask', CustomTask, 'hello', 42)
复制代码
也可以这样使用:
task myTask(type: CustomTask, constructorArgs: ['hello', 42])
复制代码
第三类是 register,register 也是用来创建新的 task 的,不过 register 执行的是延迟创建。也就是说只有当 task 被需要使用的时候才会被创建。
我们先看一个 register 方法的定义:
TaskProvider<Task> register(String name,
Action<? super Task> configurationAction)
throws InvalidUserDataException
复制代码
可以看到 register 返回了一个 TaskProvider,有点像 java 多线程中的 callable,当我们调用 Provider.get()获取 task 值的时候,才会去创建这个 task。
或者我们调用 TaskCollection.getByName(java.lang.String)的时候也会创建对应的 task。
最后一类是 replace 方法:
Task replace(String name)
<T extends Task> T replace(String name,
Class<T> type)
复制代码
replace 的作用就是创建一个新的 task,并且替换掉同样名字的老的 task。
Task 之间的依赖
task 之间的依赖关系是通过 task name 来决定的。我们可以在同一个项目中做 task 之间的依赖:
task hello {
doLast {
println 'Hello www.flydean.com!'
}
}
task intro {
dependsOn hello
doLast {
println "I'm flydean"
}
}
复制代码
也可以跨项目进行 task 的依赖,如果是跨项目的 task 依赖的话,需要制定 task 的路径:
project('project-a') {
task taskX {
dependsOn ':project-b:taskY'
doLast {
println 'taskX'
}
}
}
project('project-b') {
task taskY {
doLast {
println 'taskY'
}
}
}
复制代码
或者我们可以在定义好 task 之后,再处理 task 之间的依赖关系:
task taskX {
doLast {
println 'taskX'
}
}
task taskY {
doLast {
println 'taskY'
}
}
复制代码
还可以动态添加依赖关系:
task taskX {
doLast {
println 'taskX'
}
}
// Using a Groovy Closure
taskX.dependsOn {
tasks.findAll { task -> task.name.startsWith('lib') }
}
task lib1 {
doLast {
println 'lib1'
}
}
task lib2 {
doLast {
println 'lib2'
}
}
task notALib {
doLast {
println 'notALib'
}
}
复制代码
定义 task 之间的顺序
有时候我们的 task 之间是有执行顺序的,我们称之为对 task 的排序 ordering。
先看一下 ordering 和 dependency 有什么区别。dependency 表示的是一种强依赖关系,如果 taskA 依赖于 taskB,那么执行 taskA 的时候一定要先执行 taskB。
而 ordering 则是一种并不太强列的顺序关系。表示 taskA 需要在 taskB 之后执行,但是 taskB 不执行也可以。
在 gradle 中有两种 order:分别是 must run after 和 should run after。
taskA.mustRunAfter(taskB)表示必须遵守的顺序关系,而 taskA.shouldRunAfter(taskB)则不是必须的,在下面两种情况下可以忽略这样的顺序关系:
第一种情况是如果 shouldRunAfter 引入了 order 循环的时候。
第二种情况是如果在并行执行的情况下,task 所有的依赖关系都已经满足了,那么也会忽略这个顺序。
我们看下怎么使用:
task taskX {
doLast {
println 'flydean.com'
}
}
task taskY {
doLast {
println 'hello'
}
}
taskY.mustRunAfter taskX
//taskY.shouldRunAfter taskX
复制代码
给 task 一些描述
我们可以给 task 一些描述信息,这样我们在执行 gradle tasks 的时候,就可以查看到:
task copy(type: Copy) {
description 'Copies the resource directory to the target directory.'
from 'resources'
into 'target'
include('**/*.txt', '**/*.xml', '**/*.properties')
}
复制代码
task 的条件执行
有时候我们需要根据 build 文件中的某些属性来判断是否执行特定的 task,我们可以使用 onlyIf :
task hello {
doLast {
println 'www.flydean.com'
}
}
hello.onlyIf { !project.hasProperty('skipHello') }
复制代码
或者我们可以抛出 StopExecutionException 异常,如果遇到这个异常,那么 task 后面的任务将不会被执行:
task compile {
doLast {
println 'We are doing the compile.'
}
}
compile.doFirst {
if (true) { throw new StopExecutionException() }
}
task myTask {
dependsOn('compile')
doLast {
println 'I am not affected'
}
}
复制代码
我们还可以启动和禁用 task:
最后我们还可以让 task 超时,当超时的时候,执行 task 的线程将会被中断,并且 task 将会被标记为 failed。
如果我们想继续执行,那么可以使用 –continue。
注意, 只有能够响应中断的 task,timeout 才有用。
task hangingTask() {
doLast {
Thread.sleep(100000)
}
timeout = Duration.ofMillis(500)
}
复制代码
task rule
如果我们想要给某些 task 定义一些规则,那么可以使用 tasks.addRule:
tasks.addRule("Pattern: ping<ID>") { String taskName ->
if (taskName.startsWith("ping")) {
task(taskName) {
doLast {
println "Pinging: " + (taskName - 'ping')
}
}
}
}
复制代码
上我们定义了一个 rule,如果 taskName 是以 ping 开头的话,那么将会输出对应的内容。
看下运行结果:
> gradle -q pingServer1
Pinging: Server1
复制代码
我还可以将这些 rules 作为依赖项引入:
task groupPing {
dependsOn pingServer1, pingServer2
}
复制代码
Finalizer tasks
和 java 中的 finally 一样,task 也可以指定对应的 finalize task:
task taskX {
doLast {
println 'taskX'
}
}
task taskY {
doLast {
println 'taskY'
}
}
taskX.finalizedBy taskY
> gradle -q taskX
taskX
taskY
复制代码
finalize task 是一定会被执行的,即使上面的 taskX 中抛出了异常。
总结
以上就是 gradle 中 task 的详解,希望大家能够喜欢。
本文已收录于 http://www.flydean.com/gradle-task-in-depth/
最通俗的解读,最深刻的干货,最简洁的教程,众多你不知道的小技巧等你来发现!
欢迎关注我的公众号:「程序那些事」,懂技术,更懂你!
评论