Gradle之task的使用

在上一篇文章中(Gradle之重新认识Gradle)做了对gradle的基本介绍,本篇文章将探索最基础的gradle构建块:project和task以及它们和API之间的映射。

一、构建块

每一个Gradle构建脚本中都会包含三个基本的构建块,project、task和property。每一个构建都至少包含一个project,一个或者多个task,在多项目的构建中一个project可以依赖于其它的project,task之间也形成一个依赖关系图来确保它们之间的执行顺序,下图显示了这种依赖:
这里写图片描述

1、项目:project,一个项目代表一个正在构建的组件(比如一个JAR文件),或者是一个想要完成的目标,如部署应用程序。每一个Gradle脚本至少定义一个项目,当构建进程启动之后,Gradle基于build.gradle文件中的配置实例化org.geadle.api.project类,在构建脚本中使用project变量引用该实例,通过project变量,你可以访问Gradle的所有特性。下图显示了该API接口和重要的方法:
这里写图片描述

2、任务:task,task有一些重要的功能:任务动作(task action)和任务依赖(task dependency)。task action定义了任务执行时最小的工作单元,task dependency定义了task之间的依赖关系,例如在某个task运行之前要运行另外一个task,尤其是需要另一个task的输出作为输入的时候。task的API表示是org.gradle.api.task接口,结构如下图:
这里写图片描述

3、属性:property,每一个project和task都提供了可以通过getter和setter方法访问其属性,Gadle允许用户扩展属性自定义一些变量。

①Gradle属性通过gradle.properties文件中的声明添加到项目中,在android项目构建中我们也经常把versionName、versionCode定义在该文件中,在module中使用。

//gradle.properties脚本:
exampleProp=myValue
someOtherProp=455

//build.gradle脚本中可以直接使用上述定义的两个变量:
assert project.exampleProp =='myValue'
/**直接引用gradle.properties文件中定义的属性  $someOtherProp */
task printGradleProperty << {
	println"Second property: $someOtherProp"
}

②扩展属性需要使用ext命名空间:

project.ext.myProp ='myValue'
ext {
	someOtherProp =123
}

/**断言 如果判断为false,则构建会失败,使用ext命名空间访问属性是可选的*/
assert myProp =='myValue1'
printlnproject.someOtherProp
ext.someOtherProp =567

二、task的使用,每一个task都是org.gradle.api.DefaultTask类型,在构建脚本中我们可以直接使用属性名使用该类中的相关属性,在底层Groovy会为你调用相关的getter和setter方法去访问这些属性。

1、为task添加动作(action):在构建脚本中,我们可以给同一个task添加多个动作,每一个task保持了一个动作列表,在运行的时候,这些动作会按顺序执行:

//build.gradle 脚本
version = '0.1-SNAPSHOT'
task printVersion {
    doFirst {
        println "Before reading the project version"
    }
    doLast {
        println "Version: $version"
    }
}
/**先添加的doFirst动作后执行,先添加的doLast动作先执行*/
printVersion.doFirst { println "Second action" }
printVersion.doFirst { println "First action" }
printVersion.doLast { println "Not Last action" }
printVersion.doLast { println "Last action" }

//运行printVersion :
> gradle printVersion 
> Task :printVersion 
First action
Second action
Before reading the project version
Version: 0.1-SNAPSHOT
Not Last action
Last action

2、定义task之间的依赖:使用dependsOn方法声明依赖一个或者多个task,被依赖的任务要在该任务之前执行,但是被依赖的多个任务之间的执行顺序是不保证的。

//build.gradle 脚本:
version = '0.1-SNAPSHOT'
task first {
    doLast {
        println "first"
    }
}
task second {
    doLast {
        println "second"
    }
}
/**指定多个task依赖*/
task printVersion(dependsOn: [second, first]) {
    doLast {
        println "Version: $version"
    }
}
task third {
    doLast {
        println "third"
    }
}
/**dependsOn 添加任务之间的依赖,声明依赖时直接按名称引用task,被依赖的任务要先被执行*/
third.dependsOn('printVersion')     

//执行脚本:
> gradle third 
> Task :first 
first
> Task :second 
second
> Task :printVersion 
Version: 0.1-SNAPSHOT
> Task :third 
third

3、终结器task:在某个task执行完成后会执行终结器task,即使该task执行失败,终结器task也会被执行:

task first {
    doLast {
        println "first"
    }
}
task second {
    doLast {
        println "second"
    }
}
/**first 被 second终结*/
first.finalizedBy second

//执行 first任务:
> gradle first
> Task :first 
first
> Task :second 
second

4、自定义一个表示版本项目的类:我们可以按照Java的习惯来编写构建脚本,下面将自定义一个表示版本号的类(ProjectVersion),该类存储version.properties配置文件里的属性,定义一个task:makeReleaseVersion,该task动态的修改版本的属性值,并将值写入version.properties文件中,所有操作都写在注释中:

//version.properties文件中的配置:
major=1
minor=1
release=false
//build.gradle 构建脚本:
/**Project 接口提供的file()方法,创建一个相对于项目目录的一个java.io.File实例
 * 这里是读取 version.properties 版本配置文件*/
ext.versionFile = file('version.properties')

/**这是一个task配置块:不同于之前的task,该task没有定义动作(doLast(),doFirst()),
 * task配置块永远在task动作之前执行*/
task loadVersion {
    project.version = readVersion()
}

ProjectVersion readVersion() {
    println 'Reading the version file.'
    /**版本文件不存在就抛出异常*/
    if (!versionFile.exists()) {
        throw new GradleException("Required version file does not exist: $versionFile.canonicalPath")
    }

    Properties versionProps = new Properties()
    /**Groovy通过该方法来讲配置文件中的属性读取到versionProps中*/
    versionFile.withInputStream { stream ->
        versionProps.load(stream)
    }
    /**在version.properties 文件中定义了major minor release属性*/
    return new ProjectVersion(versionProps.major.toInteger(), versionProps.minor.toInteger(), versionProps.release.toBoolean())
}

task printVersion {
    doLast {
        println("Version: $version")
    }
}

/** 利用ant task的 propertyfile 修改属性文件*/
task makeReleaseVersion(group: 'versioning', description: 'Makes project a release version.') {
    doLast {
        version.release = true
        ant.propertyfile(file: versionFile) {
            entry(key: 'release', type: 'string', operation: '=', value: 'true')
        }
    }
}

class ProjectVersion {
    Integer major
    Integer minor
    Boolean release

    ProjectVersion(Integer major, Integer minor) {
        this.major = major
        this.minor = minor
        this.release = Boolean.FALSE
    }

    ProjectVersion(Integer major, Integer minor, Boolean release) {
        this(major, minor)
        this.release = release
    }

    @Override
    String toString() {
        /**在release属性为false的时候才加上-SNAPSHOT后缀,
         * Gradle 总是会调用toString()方法来读取version的值,所以我们要重写该方法,以实现自定义的版本号表示*/
        "$major.$minor${release ? '' : '-SNAPSHOT'}"
    }
}

//执行printVersion任务:
> gradle printVersion
> Configure project :   //这是在配置块中输出的
Reading the version file.
> Task :printVersion 
Version: 1.1-SNAPSHOT	//读取的正是version.properties配置的相关属性值

//先执行makeReleaseVersion 任务,再执行printVersion任务,version.properties中的release属性被修改成true,输出Version后缀被去掉:
> Task :printVersion 
Version: 1.1

5、Gradle构建的生命周期:Gradle构建有3个生命周期:初始化、配置和执行,下图分别显示了这3个生命周期阶段,并解释了4中的构建脚本的代码分别对应的是哪个阶段:
这里写图片描述
在初始化阶段,会为项目创建一个Project的实例;配置块中的代码使在配置阶段被执行,该阶段适合为项目或者指定的task设置所需要的配置;含有action的task在执行阶段被执行。

6、使用自定义的task实现对配置文件(version.properties)的修改和增量式构建的支持

//version.properties 配置文件:
major=0
minor=1
release=false

//build.gradle 脚本:
/**这里使用 project-version.properties 作为配置文件,*/
ext.versionFile = file('version.properties')
task loadVersion {
    project.version = readVersion()
}

ProjectVersion readVersion() {
    println 'Reading the version file.'

    if (!ext.versionFile.exists()) {
        throw new GradleException("Required version file does not exit: $versionFile.canonicalPath")
    }

    Properties versionProps = new Properties()
    versionFile.withInputStream { stream ->
        versionProps.load(stream)
    }
    /**新建 ProjectVersion 对象,使用配置文件的属性值为对象赋值*/
    return new ProjectVersion(min: versionProps.major.toInteger(), maj: versionProps.minor.toInteger(), prodReady: versionProps.release.toBoolean())
}
/**为自定义的ReleaseVersionTask 的参数release、destFile赋值*/
task makeReleaseVersion(type: ReleaseVersionTask) {
    release = version.prodReady
    destFile = new File('version.properties')
}
task printVersion {
    doLast {
        println("Version: $version")
    }
}
/**自定义task继承DefaultTask*/
class ReleaseVersionTask extends DefaultTask {
    /**task的输入输出注解, @Input注解声明task的输入属性:release
     * OutputFile注解定义输出文件,
     * 不使用注解也可以实现修改配置文件属性值的需求,因为会默认为属性生成setter和getter方法,
     * 但是无法做到增量式构建,使用注解,Gradle会比较两次的input和output是否是最新的,
     * 只有是最新的时候(比如手动修改了配置文件),task才会被执行,达到增量式构建的效果*/
    @Input
    Boolean release
    @OutputFile
    File destFile

    ReleaseVersionTask() {
        /**设置task的属性,描述性的参数*/
        group = 'versioning'
        description = 'Makes project a release version.'
    }
    /**执行makeReleaseVersion 任务时,会将两个参数传进来,然后执行被@TaskAction注解声明的方法*/
    @TaskAction
    void start() {
        project.version.prodReady = true
        /**修改配置文件的属性值*/
        ant.propertyfile(file: destFile) {
            entry(key: 'release', type: 'string', operation: '=', value: 'true')
        }
    }
}

class ProjectVersion {
    Integer min
    Integer maj
    Boolean prodReady
    /**此例中使用了 min: versionProps.major.toInteger() 的方式为对象赋值,也可以使用构造参数*/
    @Override
    String toString() {
        "$maj.$min${prodReady ? '' : '-SNAPSHOT'}"
    }
}

//直接执行printVersion任务,输出如下:
> Task :printVersion 
Version: 1.0-SNAPSHOT
//先执行makeReleaseVersion任务,然后执行printVersion任务,输出如下:
> Task :printVersion 
Version: 1.0
//多次执行makeReleaseVersion任务,gradle输出信息如下:up-to-date代表是最新的,也就是自定义task没有被执行,因为多次执行makeReleaseVersion只有第一次需要执行
BUILD SUCCESSFUL in 2s
1 actionable task: 1 up-to-date

7、task规则:在许多时候,多个task可能做着很多类似的工作,下面以两个task说明该问题并解决,一个task用来增加项目的主版本号,另一个task增加次版本号,task的命名方式一般是以“驼峰命名法”,如:incrementMajorVersion,这两个task基本上是一样的:

task incrementMajorVersion(group: 'versioning', description: 'Increments project major version.') {
    doLast {
        String currentVersion = version.toString()
        ++version.major
        String newVersion = version.toString()
        logger.info "Incrementing major project version: $currentVersion -> $newVersion"

        ant.propertyfile(file: versionFile) {
            entry(key: 'major', type: 'int', operation: '+', value: 1)
        }
    }
}

task incrementMinorVersion(group: 'versioning', description: 'Increments project minor version.') {
    doLast {
        String currentVersion = version.toString()
        ++version.minor
        String newVersion = version.toString()
        logger.info "Incrementing minor project version: $currentVersion -> $newVersion"

        ant.propertyfile(file: versionFile) {
            entry(key: 'minor', type: 'int', operation: '+', value: 1)
        }
    }
}

合并上述两个task相似逻辑,使脚本更加简洁,执行任务incrementMajorVersionincrementMinorVersion之后,配置文件中的主次版本号分别增加1

apply plugin: 'war'
ext.versionFile = file('version.properties')

task loadVersion {
    project.version = readVersion()
}

ProjectVersion readVersion() {
    logger.quiet 'Reading the version file.'

    if (!versionFile.exists()) {
        throw new GradleException("Required version file does not exit: $versionFile.canonicalPath")
    }

    Properties versionProps = new Properties()

    versionFile.withInputStream { stream ->
        versionProps.load(stream)
    }

    new ProjectVersion(versionProps.major.toInteger(), versionProps.minor.toInteger(), versionProps.release.toBoolean())
}

/**通过添加task规则,合并相似的task。
 * 合并增加主次版本号的task*/
tasks.addRule("Pattern: increment<Classifier>Version – Increments the project version classifier.") { String taskName ->
    /**根据预定义的模式检查task的名称*/
    if (taskName.startsWith('increment') && taskName.endsWith('Version')) {
        /**给符合命名模式的task动态的添加一个doLast方法*/
        task(taskName) {
            doLast {
                /**从完整的task名称中提取类型字符串*/
                String classifier = (taskName - 'increment' - 'Version').toLowerCase()
                String currentVersion = version.toString()

                switch (classifier) {
                    case 'major': ++version.major
                        break
                    case 'minor': ++version.minor
                        break
                    default: throw new GradleException("Invalid version type '$classifier. Allowed types: ['Major', 'Minor']")
                }

                String newVersion = version.toString()
                println "Incrementing $classifier project version: $currentVersion -> $newVersion"
                /**修改配置文件里相应属性的值*/
                ant.propertyfile(file: versionFile) {
                    entry(key: classifier, type: 'int', operation: '+', value: 1)
                }
            }
        }
    }
}

class ProjectVersion {
    Integer major
    Integer minor
    Boolean release

    ProjectVersion(Integer major, Integer minor) {
        this.major = major
        this.minor = minor
        this.release = Boolean.FALSE
    }

    ProjectVersion(Integer major, Integer minor, Boolean release) {
        this(major, minor)
        this.release = release
    }

    @Override
    String toString() {
        "$major.$minor${release ? '' : '-SNAPSHOT'}"
    }
}

三、监听构建的生命周期
有时候在构建的特定的生命周期可能需要执行一些代码,生命周期事件可能发生在某一个构建阶段之前、期间或者之后,我们可以使用生命周期钩子(hook),在特定的生命周期执行一些想要的代码,下面是生命周期钩子示例图:

这里写图片描述

生成task执行图是指完成了各个task之间依赖关系的确定,task之间的依赖关系被描述成一个有向无环图。通过实现TaskExecutionGraphListener接口,注册监听器,可以监听执行图事件,执行release任务后,配置文件被修改:

apply plugin: 'war'
ext.versionFile = file('version.properties')

task loadVersion {
    project.version = readVersion()
}

ProjectVersion readVersion() {
    logger.quiet 'Reading the version file.'

    if (!versionFile.exists()) {
        throw new GradleException("Required version file does not exit: $versionFile.canonicalPath")
    }

    Properties versionProps = new Properties()

    versionFile.withInputStream { stream ->
        versionProps.load(stream)
    }

    new ProjectVersion(versionProps.major.toInteger(), versionProps.minor.toInteger(), versionProps.release.toBoolean())
}
/**自定义一个监听器*/
class ReleaseVersionListener implements TaskExecutionGraphListener {
    final static String releaseTaskPath = ':release'

    @Override
    void graphPopulated(TaskExecutionGraph taskGraph) {
        println("tasks : " + taskGraph.allTasks)
        /**判断执行的tasks 是不是包含 release task,只有执行的是release task的时候才会执行下面的操作*/
        if (taskGraph.hasTask(releaseTaskPath)) {
            List<Task> allTasks = taskGraph.allTasks
            /**从执行图中的一系列task过滤release task*/
            Task releaseTask = allTasks.find { it.path == releaseTaskPath }
            /**通过task拿到project,每一个task都知道所属的project*/
            Project project = releaseTask.project
            if (!project.version.release) {
                project.version.release = true
                project.ant.propertyfile(file: project.versionFile) {
                    entry(key: 'release', type: 'string', operation: '=', value: 'true')
                }
            }
        }
    }
}
/**添加一个task执行图的监听器*/
gradle.taskGraph.addTaskExecutionGraphListener(new ReleaseVersionListener())

task release {
    doLast {
        println 'Releasing the project...'
    }
}

class ProjectVersion {
    Integer major
    Integer minor
    Boolean release

    ProjectVersion(Integer major, Integer minor) {
        this.major = major
        this.minor = minor
        this.release = Boolean.FALSE
    }

    ProjectVersion(Integer major, Integer minor, Boolean release) {
        this(major, minor)
        this.release = release
    }

    @Override
    String toString() {
        "$major.$minor${release ? '' : '-SNAPSHOT'}"
    }
}

好了,task的使用就到这吧,总结这些主要是可以让自己以后在使用Gradle的时候,虽然可能还是有很多配置操作不甚了解,但是可以让自己站在一个较高的角度理解构建脚本的逻辑,知道每一行代码是在干什么,这在编写脚本过程中还是很重要的,知其所以然,才能有更高的提升。

展开阅读全文

没有更多推荐了,返回首页