5.Gradle组件说明

1. 构建块

每个 Gradle 构建都包含三个基本构建块:project、task 和 property。每个构建包含至少-一个 project,进而又包含一个或 多个 task。 project 和 task 暴露的属性可以用来控制构建。

1.1 project

在 Gradle 术语中,一个项目(project) 代表一个正在构建的组件(比如,一个 JAR文件),或一个想要完成的目标,如部署应用程序。如果你使用过 Maven, 那么这个概念应该听起来很熟悉。Gradle 的 build.gradle 文件相当于 Maven 的 pom. xml。每个 Gradle 构建脚本至少定义一个项目。当构建进程启动后,Gradle 基于 build.gradle 中的配置实例化 org.gradle.api.Project 类,并且能够通过 project 变量使其隐式可用。下图显示了API 接口及其最重要的方法。

image-20200718170007325

1.2 task

task 的一 些重要功能:任务动作(task action)和任务依赖(task dependency)。任务动作定义了一个当任务执行时最小的工作单元。这可以简单到只打印文本如 “Hello world !” 或复杂到编译 Java 源代码。

很多时候,运行一个task之前需要运行另一个task,尤其是当task的运行需要另一个task的输出作为输入来完成自己的行动时更是如此。比如,你已经看到过在打包成一个JAR文件之前需要先编译Java源代码。让我们看看Gradle task 的API表示,org.gradle.api .Task接口,如下所示。

image-20200718170352282

1.3 property

每个 project 和 task 实例都提供了可以通过 getter 和 setter 方法访问的属性。一个属性可能是一个任务或项目的版本。

扩展属性

Gradle的很多领域模型类提供了特别的属性支持。在内部,这些属性以键值对的形式存储。为了添加属性,需要使用 ext 命名空间。

project.ext.myProp = 'myValue'

ext {
    someOtherProp = 123
}

assert myProp == 'myValue'
println project.someOtherProp
ext.someOtherProp = 567

Gradle 属性

Gradle属性可以通过在 gradle.properties 文件中声明直接添加到项目中,这个文件位于 <USER_ HOME>/ .gradle 目录或项目的根目录下。这些属性可以通过项目实例访问。记住,即使你有多个项目,每个用户也只能有一个 Gradle 属性文件在 <USER_ HOME>/ .gradle 目录下。这是目前 Gradle 对它的限制。在这个属性文件中声明的属性对所有的项目可用。我们假设下面的属性是在 gradle.properties文件中声明的:

exampleProp = myValue
someOtherProp = 455

可以按照如下方式访问这两个变量

assert project.exampleProp == 'myValue'

task printGradleProperty << {
    println "Second property: $someOtherProp"
}

声明属性的其他方式

  • 项目属性通过 -P 命令行选项提供

  • 系统属性通过 -D 命令行选项提供

  • 环境属性按照下面模式提供

    ORG_GRADLE_PROJECT_propertyName=someValue

2. 使用task

2.1 声明 task

version = '0.1-SNAPSHOT'

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

给 task 添加动作

Task 接口提供了两个方法来定义 task 的动作:doFirst(Closure) 和 doLast(Closure)

version = '0.1-SNAPSHOT'

// 声明一个包含 doFirst 和 doLast 的 task
task printVersion {
    doFirst {
        println "Before reading the project version"
    }

    doLast {
        println "Version: $version"
    }
}

// 在动作列表的开始添加 doFirst 闭包
printVersion.doFirst { println "First action" }
// 在动作列表的最后添加闭包
printVersion.doLast { println "Last action" } 

image-20200718172034922

2.2 定义task依赖

dependsOn 方法允许声明依赖一个或多个 task。

version = '0.1-SNAPSHOT'

task first {
    doLast {
        println "first"
    }
}
task second {
    doLast {
        println "second"
    }
}
// 指定多个 task 依赖
task printVersion(dependsOn: [second, first]) {
    doLast {
        logger.quiet "Version: $version"
    }
}

task third {
    doLast {
        println "third"
    }
}
// 声明依赖时按名称引用task
third.dependsOn('printVersion')     

通过调用 task third 来执行依赖链上其他 task,gradle -q third

image-20200718172555286

2.3 终结器 task

在实践中,你会发现所依赖的 task 执行后需要清理某种资源。一个典型的例子 就是 Web 容器需要对已经部署的应用程序运行集成测试。针对这种情景 Gradle提供了终结器 task(finalizertask),即使终结器 task 失败了,Gradle 的 task 也会按预 期运行。”下面的代码片段展示了如何通过使用 Task 方法 finalizedBy 来使用一个 特定的终结器task。

task first {
    doLast {
        println "first"
    }
}
task second {
    doLast {
        println "second"
    }
}
// 声明一个 task 被另一个终结
first.finalizedBy second

gradle -q first

image-20200718173150154

2.4 task 配置

version.properties

major=0
minor=1
release=false

build.gradle

// Project 提供的file方法,创建一个相对于目录的java.io.File实例
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 exist: $versionFile.canonicalPath")
    }

    Properties versionProps = new Properties()

    versionFile.withInputStream { stream ->
        // 读取文件
        versionProps.load(stream)
    }

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

task printVersion {
    doLast {
        logger.quiet "Version: $version"
    }
}

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'}"
    }
}

image-20200718174053525

2.5 task的input和output

Gradle通过比较两个构建task的inputs和outputs来决定task是否是最新的,如图4.6所示。自从最后一个task 执行以来,如果inputs和outputs没有发生变化,则认为task是最新的。因此,只有当inputs和outputs不同时,task 才运行;否则将跳过。

image-20200718174219332

version.properties

major=0
minor=1
release=true

gradle.build

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 makeReleaseVersion(group: 'versioning', description: 'Makes project a release version.') {
    inputs.property('release', version.release)
    outputs.file versionFile

    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() {
        "$major.$minor${release ? '' : '-SNAPSHOT'}"
    }
}

image-20200718175909781

2.6 自定义task

编写自定的 task 类

编写一个继承自 Gradle 默认 DefaultTask 的类

class ReleaseVersionTask extends DefaultTask {
    // 通过注解声明task的输入/输出
    @Input Boolean release
    @OutputFile File destFile

    // 在构造器中设置task的group和description属性
    ReleaseVersionTask() {
        group = 'versioning'
        description = 'Makes project a release version.'
    }

    // 使用注解声明将被执行的方法
    @TaskAction
    void start() {
        project.version.release = true
        ant.propertyfile(file: destFile) {
            entry(key: 'release', type: 'string', operation: '=', value: 'true')
        }
    }
}

使用自定义task

// 定义一个增强的 ReleaseVersionTask 类型的 task
task makeReleaseVersion(type: ReleaseVersionTask) {
    // 设置自定义 task 的属性
    release = version.release
    destFile = versionFile
}

2.7 Gradle 内置task

Gradle 的内置 task 类型都是 DefaultTask 的派生类。因此,它们可以被构建脚本中的增强的 task 使用。Gradle 提供了广泛的 task 类型,但是在这个例子中只使用两个。下面的清单显示了在产品发布过程中用到的task类型 Zip 和Copy。

task createDistribution(type: Zip, dependsOn: makeReleaseVersion) {
    from war.outputs.files

    from(sourceSets*.allSource) {
        into 'src'
    }

    from(rootDir) {
        include versionFile.name
    }
}

task backupReleaseDistribution(type: Copy) {
    from createDistribution.outputs.files
    into "$buildDir/backup"
}

task release(dependsOn: backupReleaseDistribution) {
    doLast{
        logger.quiet 'Releasing the project...'
    }
}

2.8 task规则

有时候你可能会发现在某些情况下,自己所编写的多个task却做着类似的事情。例如,你想通过两个task扩展版本管理功能:一个用来增加项目的主版本,另一个对于次版本类别做同样的事情。假定这两个task都会将变化持续保存到版本文件中。

如果你比较下面清单中这两个 task 的 doLast 行为,就会发现主要是复制了代码并应用了次版本的变化。

incrementMajorVersion

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 的property来增加属性文件中的特定属性
        ant.propertyfile(file: versionFile) {
            entry(key: 'major', type: 'int', operation: '+', value: 1)
        }
    }
}

incrementMinorVersion

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 的property来增加属性文件中的特定属性
        ant.propertyfile(file: versionFile) {
            entry(key: 'minor', type: 'int', operation: '+', value: 1)
        }
    }
}

合并相似逻辑到 task 规则中

tasks.addRule("Pattern: increment<Classifier>Version – Increments the project version classifier.") { String taskName ->
    if (taskName.startsWith('increment') && taskName.endsWith('Version')) {
        task(taskName) {
            doLast {
                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()
                logger.info "Incrementing $classifier project version: $currentVersion -> $newVersion"

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

使用 gradle tasks 查看

image-20200718182548343

3. 构建

假设你想在开发周期中尽可能早地获得失败构建的反馈信息。对失败构建一个典型的反应是发送邮件给团队中的所有开发人员,以使代码恢复正常。有两种方式可以编写回调生命周期事件:在闭包中,或者是通过 Gradle API 所提供的监听器接口实现。

Gradle 不会引导你采用哪种方式去监听生命周期事件,这完全取决于你的选择。采用监听器实现最大的优势在于你处理的类通过编写单元测试是完全可测试的。下面为你提供一个有用的生命周期钩子(hook) 的想法,如图4.11 所示。

image-20200718182947161

3.1 连接task执行

在配置时,Gradle 决定了在执行阶段要运行的task的顺序。

image-20200718183157569

通过生命周期钩子实现发布版本功能

gradle.taskGraph.whenReady { TaskExecutionGraph taskGraph ->
    // 查看 task图 中是否包含 task release
    if (taskGraph.hasTask(release)) {
        if (!version.release) {
            version.release = true
            ant.propertyfile(file: versionFile) {
                entry(key: 'release', type: 'string', operation: '=', value: 'true')
            }
        }
    }
}

task createDistribution(type: Zip) {
    from war.outputs.files

    from(sourceSets*.allSource) {
        into 'src'
    }

    from(rootDir) {
        include versionFile.name
    }
}

task backupReleaseDistribution(type: Copy) {
    from createDistribution.outputs.files
    into "$buildDir/backup"
}

task release(dependsOn: backupReleaseDistribution) {
    doLast {
        logger.quiet 'Releasing the project...'
    }
}

3.2 实现task执行图监听器

用于监听 task执行图 事件的接口是由 TaskExecutionGraphListener 提供的。构建监听器需要实现 graphPopulated 方法。

通过声明周期监听器实现发布版本功能

class ReleaseVersionListener implements TaskExecutionGraphListener {
    final static String releaseTaskPath = ':release'

    @Override
    void graphPopulated(TaskExecutionGraph taskGraph) {
        if (taskGraph.hasTask(releaseTaskPath)) {
            List<Task> allTasks = taskGraph.allTasks
            Task releaseTask = allTasks.find { it.path == releaseTaskPath }
            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())
posted @ 2020-07-19 13:40  Soulballad  阅读(226)  评论(0编辑  收藏  举报