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

深入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之间的依赖来完成我们的构建

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容