Gradle通关系列(四)-深入Task

1,260 阅读5分钟

深入Task

task作为Gradle构建的最小原子工作,可以通过task之间的相互依赖灵活的定义一个项目的构建。

task包含一下属性

  • task自身的相关属性,包含所有的getter、setter方法
  • extentsions属性
  • conventions属性
  • extra属性

Task配置

定义Task

可以为Project定义一系列的任务项,每个任务都会有自己一系列的Action,可以通过doFirst、doLast添加action

//创建默认task
tasks.register("hello") {
  	//配置阶段的代码
  	group = "custiom"
    doLast {
      	//执行阶段的代码
        println("hello")
    }
}
//创建基于Task模板的task
tasks.register<Copy>("copy") {
  	//配置拷贝源
    from(file("srcDir"))
  	//配置拷贝目的
    into(buildDir)
}

获取Task

可以获取已经定义的task,获取task的相关配置或者对其进行再次配置

//通过名称获取task
println(tasks.named("hello").get().name)
//通过名称获取指定类型的task
println(tasks.named<Copy>("copy").get().destinationDir)
//根据类型获取task
tasks.withType<Copy>().configureEach {
    group = "customCopy"
}

我们可以在TaskContainer源码中查看更多关于获取task的api

Task依赖及排序

一个task可能有依赖另外一个task,也可能需要被放在某个task之后执行,Gradle确保在执行任务时遵守所有的任务依赖关系和排序规则,使用dependsOn来操作task的依赖,使用mustRunAfter、shouldRunAfter来操作task的执行顺序。

通过以下对象来指定task依赖或排序

  • task字符串路径
  • Task对象
  • TaskDenpendcy对象
  • TaskRefrence对象
  • RegularFileProperty、File、DirectoryProperty对象
  • 包含上述类型返回值的Provider对象
  • 包含上述类型的集合
  • 包含上述类型的闭包
  1. 依赖其他项目的task
project("project-a") {
  	//依赖项目b的taskY
    tasks.register("taskX") {
      	//task的路径通过:分割
        dependsOn(":project-b:taskY")
        doLast {
            println("taskX")
        }
    }
}

project("project-b") {
    tasks.register("taskY") {
        doLast {
            println("taskY")
        }
    }
}

执行taskX之前会执行taskY

  1. 依赖一个闭包
val taskX by tasks.registering {
    doLast {
        println("taskX")
    }
}

//依赖一个闭包
taskX {
    dependsOn(provider {
        tasks.filter { task -> task.name.startsWith("lib") }
    })
}

tasks.register("lib1") {
    doLast {
        println("lib1")
    }
}

tasks.register("lib2") {
    doLast {
        println("lib2")
    }
}

tasks.register("notALib") {
    doLast {
        println("notALib")
    }
}

执行taskX之前会执行lib1、lib2

  1. 对task的执行流程进行排序
val taskX by tasks.registering {
    doLast {
        println("taskX")
    }
}
val taskY by tasks.registering {
    doLast {
        println("taskY")
    }
}
taskY {
    mustRunAfter(taskX)
}

执行以下命令的输出情况

> gradle -q taskY taskX
taskX
taskY
> gradle -q taskY
taskY

可以看出来依赖与顺序的区别

当一个task依赖其他task时,会优先执行依赖的task

task的执行顺序,并不意味着作为参照的task将被执行,只是在需要一起执行时,按照约定的先后顺序执行

  1. 当task已经有依赖流程了,会忽略排序流程
val taskX by tasks.registering {
    doLast {
        println("taskX")
    }
}
val taskY by tasks.registering {
    doLast {
        println("taskY")
    }
}
val taskZ by tasks.registering {
    doLast {
        println("taskZ")
    }
}
taskX { dependsOn(taskY) }
taskY { dependsOn(taskZ) }
taskZ { shouldRunAfter(taskX) }
> gradle -q taskX
taskZ
taskY
taskX

跳过task

  1. 通过判断条件

    val hello by tasks.registering {
        doLast {
            println("hello world")
        }
    }
    
    hello {
        onlyIf { !project.hasProperty("skipHello") }
    }
    
  2. 通过异常,抛出StopExecutionException

  3. 设置不可用

    val disableMe by tasks.registering {
        doLast {
            println("This should not be printed if the task is disabled.")
        }
    }
    
    disableMe {
        enabled = false
    }
    
    
  4. 设置task超时时间

    tasks.register("hangingTask") {
        doLast {
            Thread.sleep(100000)
        }
        timeout.set(Duration.ofMillis(500))
    }
    

给TaskContainer添加Rule

TaskContainer继承自NamedDomainObjectCollection,它可以添加一个规则,当给定一个未知命名的domain object时,会应用改规则,你可以进行忽略,或者自行创建该命名的domain object

tasks.addRule("Pattern: ping<ID>") {
    val taskName = this
    if (startsWith("ping")) {
        task(taskName) {
            doLast {
                println("Pinging: " + (taskName.replace("ping", "")))
            }
        }
    }
}

自定义Task模版

定义简单的task Class

open class GreetingTask : DefaultTask() {
    var greeting = "hello from GreetingTask"
		//TaskAction中写task的具体执行逻辑,此方法是在执行阶段执行
    @TaskAction
    fun greet() {
        println(greeting)
    }
}

tasks.register<GreetingTask>("hello")

通过setter方法配置task

tasks.register<GreetingTask>("greeting") {
  	//配置greeting参数
    greeting = "greetings from GreetingTask"
}

通过构造方法配置task

open class GreetingTask() : DefaultTask() {
    var greeting = "hello from GreetingTask"

    @javax.inject.Inject
    constructor(greeting: String) : this() {
        this.greeting = greeting
    }

    @TaskAction
    fun greet() {
        println(greeting)
    }
}
//直接传递构造函数的参数
tasks.register<GreetingTask>("greeting", "hello gradle")

通过命令行选项配置task

open class GreetingTask() : DefaultTask() {
    @Option(option = "m", description = "配置greeting文本")
    var greeting = "hello from GreetingTask"

    @TaskAction
    fun greet() {
        println(greeting)
    }
}

tasks.register<GreetingTask>("greeting")
//执行命令gradlew greeting -m hellogradle

增量构建

为了提升Gradle的构建效率,避免进行重复的工作,Gradle引入了增量构建的概念。

在大多数情况下,task一般都会包含输入和输出,以Gradle通关系列(三)中的ZipResTask为例,资源文件就是输入,打包的zip文件就是输出。 如果多次执行一个Task时的输入和输出是一样的,那么我们便可以认为这样的Task是没有必要重复执行的 。 每个Task都拥有inputs和outputs属性,他们的类型分别为TaskInputs和TaskOutputs。 在增量式构建中,我们可以为每个Task定义输入(inputs)和输入(outputs),如果在执行一个Task时,如果它的输入和输出与前一次执行时没有发生变化,那么Gradle便会认为该Task是最新的(UP-TO-DATE),因此Gradle将不予执行。一个Task的inputs和outputs可以是一个或多个文件,可以是文件夹,还可以是Project的某个Property,甚至可以是某个闭包所定义的条件 。

改造ZipResTask为增量构建

//custom_build.gradle.kts
import org.gradle.kotlin.dsl.support.zipTo

open class ZipResExtensions {
    var resPath: String = ""
    var outputPath: String = ""
}

extensions.create<ZipResExtensions>("zipRes")

abstract class ZipResTask : DefaultTask() {
    @get:InputDirectory
    abstract val resDir: Property<File>

    @get:OutputFile
    abstract val outputFile: Property<File>

    @TaskAction
    fun zipRes() {
        zipTo(outputFile.get(), resDir.get())
    }
}

tasks.register("zipRes", ZipResTask::class)

afterEvaluate {
    tasks.named("zipRes", ZipResTask::class) {
        val zipResExtensions = project.extensions.getByName<ZipResExtensions>("zipRes")
        resDir.set(file(zipResExtensions.resPath))
        outputFile.set(file(zipResExtensions.outputPath))
    }
}

执行zipRes的输出情况

第一次执行

16:39:11: Executing task 'zipRes'...

> Task :zipRes

BUILD SUCCESSFUL in 88ms
1 actionable task: 1 executed
16:39:11: Task execution finished 'zipRes'.

第二次执行

16:39:57: Executing task 'zipRes'...

> Task :zipRes UP-TO-DATE

BUILD SUCCESSFUL in 83ms
1 actionable task: 1 up-to-date
16:39:57: Task execution finished 'zipRes'.

没有改动就是直接跳过该task的执行,后面标记UP-TO-DATE

总结

task是Gradle构建的最小原子工作,我们需要会创建task、并配置它、调整各种task之间的依赖来完成我们的构建