Gradle | Task进阶学习

Task类图

Gradle 所说的 Taskorg.gradle.api.Task 接口,默认实现是 org.gradle.api.DefaultTask 类,其类图大概如下(只节选了比较重要的部分):
Task类图
我们只需关注 Task 接口即可,从这里基本可以了解到 Task 有哪些特性。

Task接口解析

为了了解每个接口方法的含义,我们直接用代码来测试验证下:

class SayHelloTask extends DefaultTask {

    String msg = "default name";
    int age = 20

    @TaskAction
    void sayHello() {
        println "Hello $msg ! Age is ${age}"
    }

}

task test1 << {
    println "task test1 exec..."
}
task test2 << {
    println "task test2 exec..."
}
task test3 << {
    println "task test3 exec..."
}
task hello(type: SayHelloTask, group: "MyGroup")

//对task进行配置,
hello.configure {
    println "hello task configure"
    msg = "hjy"
}

//获取task的名称
println "task name is ${hello.getName()}"
//获取task的组名
println "task group is ${hello.getGroup()}"

//设置task里的属性值,设置 age = 70
hello.setProperty("age", 70)
//获取task里的某个属性值
println "task msg is ${hello.property('msg')}"

//设置依赖的task,只有test1 task执行完后才会执行hello task
hello.dependsOn(test1)
//设置终结者任务,执行完hello task之后会执行test2 task,通常可以用该方法做一些清理操作
hello.finalizedBy(test2)

//如果同时执行hello、test3这2个task,会确保test3执行完之后才执行hello这个task,用这个来保证执行顺序
hello.setMustRunAfter([test3])

//设置满足某个条件后才执行该task
hello.setOnlyIf {
    //只有当 age = 70 时,才会执行task,否则不会执行
    return hello.property("age") == 70
}

我们执行任务gradle hello test3,结果如下:

> Configure project :
hello task configure
task name is hello
task group is MyGroup
task msg is hjy

> Task :test3
task test3 exec...

> Task :test1
task test1 exec...

> Task :hello
Hello hjy ! Age is 70

> Task :test2
task test2 exec...

TaskContainer接口解析

TaskContianer 是用来管理所有的 Task 实例集合的,可以通过 Project.getTasks() 来获取 TaskContainer 实例。

org.gradle.api.tasks.TaskContainer接口:
//查找task
findByPath(path: String): Task
getByPath(path: String): Task
getByName(name: String): Task
withType(type: Class): TaskCollection
matching(condition: Closure): TaskCollection

//创建task
create(name: String): Task
create(name: String, configure: Closure): Task 
create(name: String, type: Class): Task
create(options: Map<String, ?>): Task
create(options: Map<String, ?>, configure: Closure): Task

//当task被加入到TaskContainer时的监听
whenTaskAdded(action: Closure)

我们先来看看创建 task 的方法:

//当有task创建时
getTasks().whenTaskAdded { Task task ->
    println "The task ${task.getName()} is added to the TaskContainer"
}

//采用create(name: String)创建
getTasks().create("task1")

//采用create(options: Map<String, ?>)创建
getTasks().create([name: "task2", group: "MyGroup", description: "这是task2描述", dependsOn: ["task1"]])

//采用create(options: Map<String, ?>, configure: Closure)创建
getTasks().create("task3", {
    group "MyGroup"
    setDependsOn(["task1", "task2"])
    setDescription "这是task3描述"
})

执行命令gradle -q tasks --all,查看是否创建成功,结果如下:

MyGroup tasks
-------------
task2 - 这是task2描述
task3 - 这是task3描述

Other tasks
-----------
task1

我们再来试试查找 task 的方法:

//通过名字查找指定的task
def task3 = getTasks().findByName("task3")
println "findByName() return task is " + task3

def taskList = getTasks().withType(DefaultTask)
def count = 0
//遍历所有的task,打印出其名字
taskList.all { Task t ->
    println "${count++} task name is ${t.name}"
}

Task增量构建

Gradle 支持一种叫做 up-to-date 检查的功能,也就是常说的增量构建。GradleTask 会把每次运行的结果缓存下来,当下次运行时,会检查输出结果有没有变更,如果没有变更则跳过运行,这样可以提高 Gradle 的构建速度。

通常,一个 task 会有一些输入(inputs)和一些输出(outputs),task 的输入会影响其输出结果,以官网中的一张图为例:
增量构建
图中表示一个 java 编译的 task,它的输入有2种,一是 JDK 版本号,一是源文件,它的输出结果为 class 文件,只要 JDK 版本号与源文件有任何变动,最终编译出的 class 文件肯定是不同的。当我们执行过一次编译任务后,再次运行该 task ,如果发现它的输入没有任何改变,那么它编译后的结果肯定也是不会变化的,可以直接从缓存里获取输出,这样 Gradle 会标识该 taskUP-TO-DATE,进而跳过该 task 的执行。

TaskInputs、TaskOutputs

那么怎么实现一个增量构建呢?一个增量构建必须至少指定一个输入、一个输出,从前面 Task 的类图中可以看到,Task.getInputs() 对象类型为 TaskInputsTask.getOutputs() 对象类型为 TaskOuputs,从中也可以看到inputs、outputs都支持哪些数据类型。同样以一个简单例子来说明:

task test1 {
    //设置inputs
    inputs.property("name", "hjy")
    inputs.property("age", 20)
    //设置outputs
    outputs.file("$buildDir/test.txt")

    doLast {
        println "exec task task1"
    }
}

task test2 {
    doLast {
        println "exec task task2"
    }
}

连续2次运行task,执行命令gradle test1 test2,结果如下:

//第一次的运行结果
> Task :test1
exec task task1

> Task :test2
exec task task2

BUILD SUCCESSFUL in 0s
2 actionable tasks: 2 executed

//第二次的运行结果
> Task :test2
exec task task2

BUILD SUCCESSFUL in 0s
2 actionable tasks: 1 executed, 1 up-to-date

taskInputs、taskOutputs注解

当你自定义 task class 时,可以通过注解来实现增量构建,这是一种更加灵活方便的方式。我们常用的注解包括:

注解名 属性类型 描述
@Input 任意Serializable类型t 一个简单的输入值
@InputFilet Filet 一个输入文件,不是目录
@InputDirectoryt Filet 一个输入目录,不是文件
@InputFilest Iterablev File列表,包含文件和目录
@OutputFilet Filet 一个输出文件,不是目录
@OutputDirectoryt Filet 一个输出目录,不是文件
@OutputFilest Map<String, File>或Iterablet 输出文件列表
@OutputDirectoriest Map<String, File>或Iterablet 输出目录列表

我们通过自定义一个 task class 来演示一下用法:

class SayHelloTask extends DefaultTask {

    //定义输入
    @Input
    String username;
    @Input
    int age

    //定义输出
    @OutputDirectory
    File destDir;

    @TaskAction
    void sayHello() {
        println "Hello $username ! age is $age"
    }

}

task test(type: SayHelloTask) {
    age = 18
    username = "hjy"
    destDir = file("$buildDir/test")
}

执行该 task 之后,会自动生成一个 $buildDir/test 文件目录,当你再次运行时就实施增量构建。但是当你修改 age、username 的值,或者删除磁盘上 $buildDir/test 目录,再次运行该 task ,该 task 就会重新运行。

猜你喜欢

转载自blog.csdn.net/u013700502/article/details/129919772