zoukankan      html  css  js  c++  java
  • 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())
    
  • 相关阅读:
    主成分分析(PCA)原理详解_转载
    用eclipse怎么打war包?
    dsLinq.Count() 引发了“System.NullReferenceException”类型的异常
    sql中实现split()功能
    关于SQL Server将一列的多行内容拼接成一行,合并显示在另外表中
    SEM和SEO的区别?
    最适合程序员转行的10大职业
    CPM、CPC、CPA、PFP、CPS、CPL、CPR等广告术语是什么意思
    什么是CPC,CPA,CVR,CTR,ROI
    SQL SERVER 修改数据库名称(包括 db.mdf 名称的修改)
  • 原文地址:https://www.cnblogs.com/col-smile/p/13339475.html
Copyright © 2011-2022 走看看