本文内容大部分来自 Gradle 官方文档,英文 OK 的同窗能够直接看官方文档。 本文示例代码已放在 zjxstar 的 GitHub。html
上一篇文章中,咱们学习了 Gradle 中 Project 的相关知识,也提到了简单 Task 的定义。一个 Project 里包含了多个 Task(任务),Gradle 的一系列操做都归功于 Task。本文将深刻学习 Task 的相关知识,掌握如何建立、访问和配置一个 Task。java
Task(任务)表示构建过程当中的单个原子工做,例如编译类或生成 javadoc。每一个任务都属于某个 Project,在 Project 中能够经过任务名或者 TaskContainer 来访问任务。每一个任务都有一个彻底限定的路径,该路径在全部项目的全部任务中都是惟一的。路径是所在项目路径和任务名称的串联,使用 :
字符分隔。git
当 Gradle 执行一个任务时,它能够在控制台 UI 和 Tooling API 中使用不一样的结果标记任务。这些标签基于任务是否具备要执行的操做,是否应该执行这些操做,是否执行了这些操做以及这些操做是否进行了任何更改。github
主要有如下 5 种标记:(在运行 Task 时增长 -i 或者 --info 便可查看)api
在 Gradle 中,咱们有多种方式来建立任务。主要用到 Project 提供的 task
方法以及 TaskContainer ( tasks
) 提供的 create 方法。缓存
方式一:使用字符串做为任务名建立 Task,示例:gradlew -q helloA闭包
task('helloA') {
doLast {
println("helloA task(name)")
}
}
复制代码
方式二:使用 tasks 的 create 方法,示例:gradlew -q helloBapp
tasks.create('helloB') {
doFirst {
println("helloB tasks.create(name)")
}
}
复制代码
方式三:使用 DSL 的特殊语法,示例:gradlew -q helloCide
task helloC {
doLast {
println("helloC task name")
}
}
// 执行 gradlew -q helloD 命令运行
task(helloD) {
doLast {
println("helloD task(name)")
}
}
复制代码
在建立 Task 时,能够传入一个 Map 来简单配置任务,经常使用配置项有:学习
配置项 | 描述 | 默认值 |
---|---|---|
type | 基于一个存在的 Task 来建立,相似于继承 | DefaultTask |
overwrite | 是否替换已经存在的 Task,和 type 配合使用 | false |
dependsOn | 用于配置任务依赖 | [] |
action | 添加到任务中的一个 Action 或者一个闭包 | null |
description | 用于配置任务的描述 | null |
group | 用于配置任务的分组 | null |
使用示例:
// 使用 Map 增长配置项
task copy(type: Copy)
// 覆盖了原 copy 任务
task copy(overwrite: true) {
doLast {
println('I am the new one.')
}
}
// 使用 gradlew tasks 命令查看 helloE 的配置
task helloE {
description 'i am helloE'
group BasePlugin.BUILD_GROUP
doLast {
println('this is helloE')
}
}
复制代码
以上是建立一个 Task 的基本方法。但在 build.gradle 脚本中,咱们能够利用 Groovy 语言的强大特性来动态建立多个任务。例如:gradlew -q task1
// 同时建立4个任务:task0、task一、task二、task3
4.times { counter ->
task "task$counter" {
doLast {
println "I'm task number $counter"
}
}
}
复制代码
当建立完 Task 以后,咱们能够访问它们以进行配置或者依赖。那么怎么访问一个已经定义好的 Task 呢?主要有三种方法。
方式一:使用 Groovy 中 DSL 特殊语法访问。
// 承接上文示例
// 以helloE任务为例
println '任务helloE的name: ' + helloE.name
println '任务helloE的description: ' + project.helloE.description
复制代码
方式二:使用 tasks 访问任务集。
// 利用tasks
println tasks.named('helloD').get().name
println tasks.copy.doLast {
println 'configure by tasks.copy.doLast'
}
println tasks['helloC'].name
println tasks.getByName('helloB').name
复制代码
方式三:经过路径访问任务
println tasks.getByPath('helloE').path // 找不到抛异常UnknownTaskException
println tasks.getByPath(':app:helloE').path
def ehelloE = tasks.findByPath("EhelloE") // 找不到返回null;
println ehelloE == null
复制代码
经过路径查找任务有两种方法,一种是 get,另外一种是 find 。它们的区别在于 get 方法若是找不到指定任务就会抛出 UnknownTaskException 异常,而 find 方法则会返回 null 。
既然可以访问到 Task,那就能够对 Task 进行一些操做了,而这些操做又涉及到 Task 的属性和方法,这里简单介绍下。
Task 的属性范围有四个。你能够经过属性名或者 Task.property( java.lang.String )
方法访问到指定属性。也能够经过 Task.setProperty( java.lang.String, java.lang.Object )
方法修改属性值。四个范围以下:
名称 -> 值
对,可用于动态地向任务对象添加属性。此范围的属性是可读写的。经常使用属性有:
属性名 | 描述 |
---|---|
actions | 该任务将要执行的一系列动做 |
dependsOn | 返回该任务依赖的任务 |
description | 任务的描述 |
enabled | 该任务是否开启 |
finalizedBy | 返回完成此任务以后的任务 |
group | 任务的分组 |
mustRunAfter | 返回该任务必须在哪一个任务以后运行的任务 |
name | 任务的名字 |
path | 任务的路径 |
project | 任务所属的 Project |
咱们举个额外属性的例子:
// 定义 Task 的额外属性
task myTask {
ext.myProperty = "myValue"
}
// 访问该属性
task printTaskProperties {
doLast {
println myTask.myProperty
}
}
复制代码
其余的属性会在下面的小节中详细介绍。
至于 Task 的方法,这里只简单列举出来:
方法名(不列出参数) | 描述 |
---|---|
dependsOn | 给任务设置依赖任务 |
doFirst | 给 Task 添加一个任务动做开始执行以前的动做 |
doLast | 给 Task 添加一个任务动做执行结束以后的动做 |
finalizedBy | 给任务添加终结任务,即该任务结束后执行的任务 |
hasProperty | 判断该任务是否有指定属性 |
mustRunAfter | 声明该任务必须在某些任务以后执行 |
onlyIf | 给任务添加断言,只有知足条件才能够执行任务 |
property | 返回指定属性的值 |
setProperty | 修改指定属性的值 |
前面的例子中常常看见 doFirst 和 doLast,下文中会对它们作详细的介绍。
Gradle 其实还给咱们提供一个 DefaultTask 基类,经过继承它能够用来自定义任务,多用在自定义 Gradle 插件中。这里简单写个示例,来讲明 DefaultTask 的用法。
// app的build.gradle
class CustomTask extends DefaultTask {
final String message
final int number
def content // 配置参数
// 添加构造参数
@Inject
CustomTask(String message, int number) {
this.message = message
this.number = number
}
// 添加要执行动做
@TaskAction
def greet() {
println content
println "message is $message , number is $number !"
}
}
// 使用tasks建立
// 须要传递两个参数,不能为null
tasks.create('myCustomTask1', CustomTask, 'hahaha', 110)
myCustomTask1.content = 'i love you'
myCustomTask1.doFirst {
println 'my custom task do first'
}
myCustomTask1.doLast {
println 'my custom task do last'
}
// 使用task建立,构造参数使用constructorArgs传递,参数不能为null
task myCustomTask2(type: CustomTask, constructorArgs: ['xixi', 120])
myCustomTask2.content = 'i hate you'
复制代码
示例中的 CustomTask 类是在 build.gradle 文件中定义的,直接继承 DefaultTask 便可。若是但愿该 Task 有可执行的动做,就须要在动做方法上添加 @TaskAction
的注解,这样 Gradle 就会将该动做添加到 Task 的动做列表中。咱们能够在类中为任务配置属性,如示例中的 content 、message 、number 。其中,message 和 number 经过 @javax.inject.Inject
注解设置成了构造参数,在建立该自定义任务时须要传递这两个参数。
讲到这里,咱们已经了解了 Task 的建立与使用,那么如今有必要对 Task 的执行作一个大概的分析,这对咱们深刻理解 Task 有很大帮助。
当咱们执行一个 Task 的时候,实际上是执行其拥有的 actions 列表,这个列表保存在 Task 对象实例的 actions 成员变量中,其类型是一个 List:
private List<ContextAwareTaskAction> actions;
复制代码
那么怎么将执行动做加入到该列表中呢?在前文的示例代码中,你们应该注意到建立 Task 的时候用到了 doFirst 和 doLast 两个方法。没错,这两个方法就能够将待执行的动做添加到 actions 列表中。
咱们粗略看下 doFirst 和 doLast 的源码实现:(这两个方法在类 org.gradle.api.internal.AbstractTask 中)
@Override
public Task doFirst(final String actionName, final Action<? super Task> action) {
...
taskMutator.mutate("Task.doFirst(Action)", new Runnable() {
public void run() {
// 每次都添加到列表头
getTaskActions().add(0, wrap(action, actionName));
}
});
return this;
}
@Override
public Task doLast(final String actionName, final Action<? super Task> action) {
...
taskMutator.mutate("Task.doLast(Action)", new Runnable() {
public void run() {
// 每次都添加到表尾
getTaskActions().add(wrap(action, actionName));
}
});
return this;
}
复制代码
能够看到,doFirst 会把动做添加到表头,而 doLast 则会把动做添加到表尾。
那怎么把动做添加到列表中间呢?是否是直接在 Task 里写动做?咱们能够试一下:
// 经过 gradlew -q greetC 执行
task greetC { // 能够显示声明 doFirst doLast
// 在配置阶段执行
println 'i am greet C, in configure'
// 在执行阶段执行
doFirst {
println 'i am greet C, in doFirst'
}
// 在执行阶段执行
doLast {
println 'i am greet C, in doLast'
}
}
复制代码
执行该任务后,你会发现 " i am greet C, in configure " 这句话没有在 doFirst 和 doLast 中间打印,而是打印在任务的配置阶段。说明直接在 Task 里写的动做并不会添加到 Task 的动做列表中,只会当作 Task 的配置信息执行。那有没有其余办法呢?
答案是确定的,这就得利用到上小节中提到的 DefaultTask 类了。咱们用 @TaskAction
注解标记的动做就会被添加到 Task 的动做列表中间。咱们直接执行上节中的 myCustomTask1 任务 ( gradlew -q myCustomTask1 ) 。结果以下:
my custom task do first // doFirst
i love you // @TaskAction
message is hahaha , number is 110 ! // @TaskAction
my custom task do last // doLast
复制代码
这样,Task 的执行顺序基本就清晰了。
这里须要提一个注意点,咱们在建立任务的时候,若是只想给任务配置一个 doFirst 的动做,可使用左移符号 <<
来表示。
task greetB << { // << 等价于 doFirst
println 'i am greet B, in doFirst'
// 不能继续配置doLast
// doLast {
// println 'i am greet B, in doLast'
// }
}
// 只能单独配置
greetB.doLast {
println 'i am greet B, in doLast'
}
复制代码
左移符号就等价于 doFirst ,并且,此时不能再给该任务配置 doLast 动做了,只能单独进行配置。
咱们知道,一个 Project 拥有多个 Task,这些 Task 之间的关系由 DAG 图维护。而 DAG 图是在构建的配置过程当中生成的,咱们能够经过 gradle.taskGraph
来监听这个过程。
举例:这个方法经常使用来给某些变量赋不一样的值。( gradlew -q greetD )
def versionD = '0.0'
task greetD {
doLast {
println "i am greet D, versionD is $versionD"
}
}
// task的DAG图是在配置阶段生成的
// 任务准备好了
gradle.taskGraph.whenReady {taskGraph ->
if (taskGraph.hasTask('greetC')) {
versionD = '1.0'
} else {
versionD = '1.0-alpha'
}
}
// 任务执行前
gradle.taskGraph.beforeTask { Task task ->
println "executing $task ..."
}
// 任务执行后
gradle.taskGraph.afterTask { Task task, TaskState state ->
if (state.failure) {
println "FAILED"
} else {
println "$task done"
}
}
复制代码
咱们能够经过监听 DAG 图的回调来对特定的 Task 进行定制化处理。
既然任务是经过 DAG 图维护的,那任务之间确定存在依赖和前后执行顺序,咱们在定义任务的时候是否也能够给任务添加依赖或者执行顺序呢?这就得利用到任务的 dependsOn 、mustRunAfter 等方法了。
dependsOn :给某个任务设置依赖任务。
task dependsA { // 定义一个基础Task
doLast {
println 'i am depends A task'
}
}
// 当执行B时,会先执行它的依赖任务A
task dependsB {
dependsOn dependsA // 经过方法设置
doLast {
println 'i am depends B task'
}
}
// 经过Map参数依赖任务A
task dependsC(dependsOn: dependsA) {
doLast {
println 'i am depends C task'
}
}
// 任务D懒依赖任务E
// 任务E后定义
task dependsD {
dependsOn 'dependsE'
doLast {
println 'i am depends D task'
}
}
task dependsE {
doLast {
println 'i am depends E task'
}
}
task dependsF {
doLast {
println 'i am depends F task'
}
}
// 经过dependsOn方法同时依赖两个任务E和A
dependsF.dependsOn dependsE, dependsA
复制代码
从示例中能够看到,经过 dependsOn 设置任务的依赖关系后,当执行任务时,其依赖的任务会先完成执行。并且,能够给某个任务同时设置多个依赖任务;也能够进行懒依赖,即依赖那些尚未定义的任务。
finalizedBy :给某个任务设置终结任务。
task taskX { // 定义任务X
doLast {
println 'i am task X'
}
}
task taskY { // 定义任务Y
doLast {
println 'i am task Y'
}
}
task taskZ { // 定义任务Z
doLast {
println 'i am task Z'
}
}
// 任务X执行后,马上执行任务Y和任务Z
taskX.finalizedBy taskY, taskZ
task taskM { // 定义任务M
doLast {
println 'i am task M'
}
}
task taskN {
finalizedBy taskM // 将任务M设置成任务N的终结任务
doLast {
println 'i am task N'
}
}
复制代码
对任务进行 finalizedBy 配置和 dependsOn 很相似,其做用和 dependsOn 刚好相反。在某任务执行完后,会执行其设置的终结任务。
mustRunAfter :若是 taskB.mustRunAfter(taskA) 则表示 taskB 必须在 taskA 执行以后再执行,这个规则比较严格。
task taskA {
doLast {
println 'i am task A'
}
}
task taskB {
doLast {
println 'i am task B'
}
}
// 任务A必须在任务B以后执行
taskA.mustRunAfter taskB
复制代码
运行命令 gradlew taskA taskB ,你会发现 taskB 会先执行。
shouldRunAfter :若是 taskB.shouldRunAfter(taskA) 则表示 taskB 应该在 taskA 以后执行,但这不是必须的,任务可能不会按预设的顺序执行。
task shouldTaskC << {
println 'i am task C'
}
task shouldTaskD << {
println 'i am task D'
}
shouldTaskC.shouldRunAfter shouldTaskD
复制代码
运行命令 gradlew shouldTaskC shouldTaskD 查看执行结果。
这里举个 shouldRunAfter 失效的例子:
task shouldTaskX {
doLast {
println 'taskX'
}
}
task shouldTaskY {
doLast {
println 'taskY'
}
}
task shouldTaskZ {
doLast {
println 'taskZ'
}
}
shouldTaskX.dependsOn shouldTaskY
shouldTaskY.dependsOn shouldTaskZ
shouldTaskZ.shouldRunAfter shouldTaskX // 这里实际上是失效的
复制代码
运行命令 gradlew -q shouldTaskX 会发现任务 Z 会先于任务 X 执行。
这里再提一个 tasks 的 whenTaskAdded 方法。若是在构建过程当中有任务添加到 project ,则会触发此回调。咱们能够监听这个回调来配置一些任务依赖或者修改某些变量,示例以下 ( gradlew HelloSecond )。
// 定义任务greetE
tasks.create('greetE') {
doLast {
println 'i am greetE'
}
}
// 构建过程当中添加任务时会触发此回调,经常使用来配置一些任务依赖或者赋值
// 经测试,该回调只针对插件中的任务有效
project.tasks.whenTaskAdded { task ->
println "task ${task.name} add"
if (task.name == 'HelloSecond') { // 执行HelloSecond任务时,会先执行greetE
task.dependsOn 'greetE'
}
}
// 定义一个自定义插件
class SecondPlugin implements Plugin<Project> {
@Override
void apply(Project project) {
println 'Hello Second Gradle Plugin'
// 添加一个task到project中
project.task('HelloSecond', {
println '===== SecondPlugin HelloSecond Task ====='
logger.quiet('hello')
})
}
}
apply plugin: SecondPlugin
复制代码
该回调只针对插件(插件的相关知识会在后续文章中给你们介绍)中定义的任务,示例中为插件中的 HelloSecond 任务添加了一个 greetE 任务依赖。这样,执行 HelloSecond 时会先执行 greetE 。
可能你有这样的需求:某些任务禁止执行或者知足某个条件才能执行。Gradle 提供了多种方式来跳过任务。
方式一:每一个任务都有个 enabled 属性,能够启用和禁用任务,默认是 true,表示启用。若是设置为 false ,则会禁止该任务执行,输出会提示该任务被跳过,即被标记成 SKIPPED 。
// 使用gradlew disableMe运行
// 输出:Task :app:disableMe SKIPPED
task disableMe {
doLast {
println 'This should not be printed if the task is disabled.'
}
}
disableMe.enabled = false // 禁止该任务执行
复制代码
方式二:使用 onlyIf 判断方法。只有当 onlyIf 里返回 true 时该任务才能够执行。
// 使用gradlew sayBye -PskipSayBye运行
// 这里的-P是添加参数的意思
task sayBye {
doLast {
println 'i am sayBye task'
}
}
// 只有当project中没有 skipSayBye 属性时,任务才能够执行
sayBye.onlyIf { !project.hasProperty('skipSayBye') }
复制代码
方式三:使用 StopExecutionException 。若是任务抛出这个异常,Gradle 会跳过该任务的执行,转而去执行下一个任务。
// 使用gradlew nextTask运行
task byeTask {
doLast {
println 'We are doing the byeTask.'
}
}
// 不会影响后续任务的执行
byeTask.doFirst {
// Here you would put arbitrary conditions in real life.
// But this is used in an integration test so we want defined behavior.
if (true) { throw new StopExecutionException() }
}
task nextTask {
dependsOn('byeTask') // 先执行byeTask,而byeTask会异常中断
doLast { // 并不影响nextTask的执行
println 'I am not affected'
}
}
复制代码
方式四:利用 Task 的 timeout 属性来限制任务的执行时间。一旦任务超时,它的执行就会被中断,任务将被标记失败。Gradle 中内置任务都能及时响应超时。
// 故意超时
task hangingTask() {
doLast {
Thread.sleep(100000)
}
timeout = Duration.ofMillis(500)
}
复制代码
虽然有四种方法,但经常使用的仍是方法一和方法二。
若是咱们在要执行某个不存在的任务时,Gradle 会直接报异常提示找不到该任务。其实,咱们能够经过添加规则的方式来作自定义处理。这须要利用到 TaskContainer 的 addRule 方法。
// 第一个参数是该规则的描述
// 第二个闭包参数是该规则要执行的动做
tasks.addRule("Pattern: ping<ID>") { String taskName ->
if (taskName.startsWith("ping")) {
task(taskName) {
doLast {
println "Pinging: " + (taskName - 'ping')
}
}
}
}
复制代码
例子中添加了一个规则:若是运行的任务是以 ping 开头的,则会建立该任务(该任务运行前并不存在),并赋予 doLast 操做。可使用 gradlew pingServer1 执行。
咱们不只能够经过命令行来使用规则,也能够在基于规则的任务上建立 dependsOn 关系。
// gradlew groupPing
task groupPing {
dependsOn pingServer1, pingServer2
}
复制代码
生命周期任务是不能自行完成的任务,它们一般没有任何执行动做。这些任务主要体如今:
除非生命周期任务具备操做,不然其结果由其依赖性决定。 若是执行任何任务的依赖项,则将认为生命周期任务已执行。 若是全部任务的依赖项都是最新的,跳过的或来自缓存,则生命周期任务将被视为最新的。
读完本文内容,相信你已经学会了如何建立和使用 Task 。Task 做为 Gradle 的主要执行骨架是很是重要的,咱们能够经过 Task 的各类属性、方法来灵活地配置和调整任务的依赖、执行顺序以及运行规则。你们不妨多写一些示例,有助于理解 Gradle 的工做机制,也为后续的自定义插件奠基基础。