深入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对象
- 包含上述类型的集合
- 包含上述类型的闭包
- 依赖其他项目的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
- 依赖一个闭包
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
- 对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将被执行,只是在需要一起执行时,按照约定的先后顺序执行
- 当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
-
通过判断条件
val hello by tasks.registering { doLast { println("hello world") } } hello { onlyIf { !project.hasProperty("skipHello") } }
通过异常,抛出StopExecutionException
-
设置不可用
val disableMe by tasks.registering { doLast { println("This should not be printed if the task is disabled.") } } disableMe { enabled = false }
-
设置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之间的依赖来完成我们的构建