美文网首页
Gradle实战读书笔记之一 Gradle 构建块

Gradle实战读书笔记之一 Gradle 构建块

作者: zhaoyubetter | 来源:发表于2017-08-23 17:48 被阅读65次

    Gradle的一些命令记录:

    1. 执行一个指定的任务:
      gradle -q taskName // 如:gradle -q hello
    2. 查看所有任务
      gradle -q tasks
    3. 执行多个任务
      gradle hello hello2
    4. 排除任务的执行 -x:
      gradle hello -x hello2 // 排除hello2执行

    org.gradle.api.Project 项目

    在Gradle中,一个项目(project)表示一个正在构建的组件(如:一个jar文件,android中,一个aar文件);
    Gradle的build.gradle文件相当于Maven的pom.xml,每个build.gradle文件在构建启动后,对应于org.gradle.api.Project的一个实例,并且能够通过project变量使其隐式可用;

    Project类接口

    一个Project可用创建新的task,添加依赖与配置,实际上这些都是 project接口的方法实现的,如下dependencies 就是调用 Project的 dependencies 方法,并传递闭包;

    dependencies {
        provided 'com.android.support:support-annotations:25.3.1'
        provided 'com.android.support:support-v4:25.3.1'
    }
    

    构建Project实例中,可通过代码访问Gradle的所有特性,如:task创建、依赖管理等;当访问属性与方法时,不需要使用project变量;

    setDescription("myProject")  // 没有显示指定 project变量
    println "Description of project $name: " + project.description;  // 访问name与description属性;
    

    在Android开发中,每个module都表示一个Project,有自己独立的 build.gradle脚本;

    org.gradle.api.Task 任务

    任务task对应的是 org.gradle.api.Task接口;任务的一些重要功能有:任务动作(task action)和任务依赖(task dependency);
    任务动作:定义了一个当任务执行时最小的工作单元;
    任务依赖:有些时候,某个任务的运行之前需要其他Task先运行;

    Task接口

    属性

    每个ProjectTask实例都提供可通过 getter与setter方法访问的属性,如:项目名,任务名等;
    通常,我们需要自定义一些自己的属性;比如:Android的编译版本,minSdk等等;这就用到扩展属性;

    扩展属性
    在内部,这些属性以键值对的形式存储,添加属性,使用 ext命名空间,在定义扩展属性的时候,需要使用到 ext 命名空间前缀,如下 :

    // 此扩展属性信息,定义在一个单独的gradle文件中,使用的使用
    // 在项目的根build.gralde文件中 apply from: "文件名"来引用;
    
    // gradle配置信息
    ext {
        COMPILE_SDK_VERSION = 25           // 25 23
        BUILD_TOOLS_VERSION = "25.0.2"   // 25.0.2 23.0.2
    
        MIN_SDK_VERSION = 14
        TARGET_SDK_VERSION = 23     // 这个千万不能改
    
        // support依赖支持包
        compile_support = [
                design      : "com.android.support:design:25.3.1",
                annotations : "com.android.support:support-annotations:25.3.1",
                appcompat   : "com.android.support:appcompat-v7:25.3.1",
                recyclerview: "com.android.support:recyclerview-v7:25.3.1",
        ]
    
        // 常用库包
        compile_common = [
                gson: "com.google.code.gson:gson:2.7"
        ]
    }
    

    使用的时候,可省略ext前缀,如下:

    android {
        compileSdkVersion COMPILE_SDK_VERSION
        buildToolsVersion BUILD_TOOLS_VERSION
        ....
    

    Gradle属性
    Gradle属性可通过gradle.properties文件中声明直接添加到项目中,此文件位于 <USER_HOME>/.gradle.properties目录 或 项目的根目录下,这些属性可以通过项目实例访问;
    如果一个project有多个module,也只能有一份 gradle.properties文件;

    project下的gradle属性文件

    比如,在properties文件声明了(最好在本项目的文件做实验,不动全局的,见下解释):
    myname=zhaoyubetter
    通过直接引用名字来访问
    println(myname) // 来进行访问;

    注意:在android studio 中,android视图中,显示的gradle.properties文件是全局的,与操作系统的是同一个文件,如下:

    gradle.properties全局文件

    项目中的 gradle.properties文件需要切换到 project files 视图,才能看到,如下:

    项目的gradle.properties文件

    使用Task

    默认情况下,每个新创建的Task都是org.gradle.api.DefaultTask类型的;

    声明task动作
    动作(action)就是在task中合适的地方放置构建逻辑,Task接口提供2个相关的方法来声明task动作:doFirst(Closure) (可使用 >>)与 doLast(Closure) (可使用<<左移);

    def curVersion = '1.0.0-SNAPSHOT'
    task printVersion {
        doLast {
            println ">>>>>>>>>>>>>>>>>>verson:${curVersion}"
        }
    }
    
    // or 写成
    task printVersion << {
         println ">>>>>>>>>>>>>>>>>>verson:${curVersion}"
    }
    

    通过 gradle printVersion 可执行此任务;

    访问DefaultTask属性

    • description属性:描述任务的作用;
    • group属性: 用于定义task的逻辑分组;
      创建Task时,可传递,作为参数:
    def version = '1.0.0-SNAPSHOT'
    task printVersion(group: 'versioning', description: 'Print project version') {
        doFirst {
            println ">>>>> before reading the version"
        }
        doLast {
            println ">>>>> version: $version"
        }
    }
    
    // 或者
    task printVersion() {
        group = 'versioning'
        description =  'Print project version'
    

    定义Task依赖

    dependsOn方法,允许声明依赖一个或多个task,通过task依赖关系图来建模完整的task生命周期;

    def group = 'myGroup'
    task first << { println ">>>> first" }
    task second << { println ">>>> second" }
    // 依赖多个任务
    task printVersion(group: group, dependsOn: [second, first]) << {
        println ">>>> the version : $version"
    }
    task third << { println ">>> third" }
    third.dependsOn('printVersion')  // 声明依赖时,按名称引用task
    

    输入:gradle -q third, 输出如下:

    依赖输出,顺序是乱的

    task 依赖的执行顺序
    Gradle不能保证依赖的执行顺序,dependsOn只是定义了所依赖的task需要先执行;在gradle中,执行顺序是由task的输入/输出规范自动确定的;

    终结器Task

    使用 task 的finalizedBy来使用一个特定的终结器:

    task first << { println ">>>> first" }
    task second << { println ">>>> second" }
    first.finalizedBy second        // 执行 gradle -q first 时,会触发 task second
    

    添加任意代码,设置版本信息

    gradle就是groovy,所以可以加入一些groovy代码,如下:

      // 版本
      class ProjectVersion {
        int major
        int minor
        int min
    
        boolean release
    
        ProjectVersion(int major, int minor, int min) {
            this.major = major
            this.minor = minor
            this.min = min
            this.release = false
        }
    
        ProjectVersion(int major, int minor, int min, boolean release) {
            this.major = major
            this.minor = minor
            this.min = min
            this.release = release
        }
    
    
        @Override
        public String toString() {
            return "$major.$minor.$min${release ? '' : '-SNAPSHOT'}"
        }
    }
    
    def version = new ProjectVersion(1, 0, 0)
    task printVersion << {
        println(" >>>>>> the verson is ${version}")
    }
    

    添加task配置块

    task配置块会在task动作之前被执行;

    1. 新建配置文件version.properties如下:
    major=1
    minor=0
    min=0
    release=false
    
    1. 添加Gradle配置 loadVersion(没有 <<>> 运算符) ,并读取 properties文件
    ext.versionFile = file('version.properties')
    // loadVersion任务,没有 << or >> 运算符,Gradle称之为 task配置
    task loadVersion {
        project.version = readVersion()
    }
    
    ProjectVersion readVersion() {
        println(">>>>> reading the version file")
        if(!versionFile.exists()) {
            throw new RuntimeException(">>> Required version file does not exist: ${versionFile.canonicalPath}")
        }
    
        Properties prop = new Properties()
        versionFile.withInputStream { stream ->
            prop.load(stream)
        }
    
        new ProjectVersion(prop.major.toInteger(), prop.minor.toInteger(), prop.min.toInteger(), prop.release.toBoolean())
    }
    
    task printVersion << {
        println(" >>>>>> the verson is ${version}")     // 访问project的属性version
    }
    

    Gradle构建生命周期阶段
    执行gradle构建,会运行三个不同的生命周期阶段:初始化、配置和执行;
    如下图:

    Gradle构建生命周期的构建阶段的顺序
    1. 初始化阶段:gradle为项目创建一个Project实例;
    2. 配置阶段:Gradle构造一个模型来表示任务,此阶段用来为项目或指定的task设置所需的配置;项目的每一次构建的任何配置代码都可以被执行,即使只执行 gradle tasks;
    3. 执行阶段:执行task动作;执行的顺序由他们的依赖决定;如果任务被认为没有修改过,将跳过;

    声明task的inputs和outputs

    Gradle通过比较2个构建task的inputs和outputs来决定是否是最新的;当 inputs和outputs不同时,task才运行,否则跳过;
    输入可以是一个目录,文件或属性等,一个task的输出是通过一个目录或1~n个文件来定义的;
    inputs与outputs在DefaultTask类中,被定义为属性或者直接子类来表示;

    DefaultTask类定义的task的inputs和outputs

    创建Task用来发布产品版本,并自动修改配置文件

    // 产品发布版本
    task makeReleaseVersion(group: 'versioning', description: 'Makes project a reelase version.') << {
        version.release = true
        // ant task 的propertyfile 提供便利的方式修改属性文件 (version.properties)
        ant.propertyfile(file: versionFile) {
            entry(key: 'release', type: 'string', operation: '=', value: 'true')
        }
    }
    

    通过运行 :gralde makeReleaseVersion 可以发现配置文件,确实修改了;

    但Gradle并不知道 我们的 是发布版本,为了解决,需要声明它的inputs与outputs

    task makeReleaseVersion(group: 'versioning', description: 'Makes project a reelase version.') {
        // 在配置阶段声明 inputs/outputs
        inputs.property('release', version.release)   // 声明版本的release属性作为输入
        outputs.file versionFile                      // 由于版本文件被修改,所以他被声明最为输出文件属性
    
        doLast {    // 闭包为动作代码
            version.release = true
            ant.propertyfile(file: versionFile) {
                entry(key: 'release', type: 'string', operation: '=', value: 'true')
            }
            println(">>>>>> makeReleaseVersion")
        }
    }
    

    task的inputs和outputs是在配置阶段执行的用来连接task依赖,需要在配置块中被定义,而且需要确保赋给 inputs和outputs的值在配置阶段是可访问的;
    如果需要编程获得输出,可通过 TaskOutputs上的upToDateWhen(Closure)方法实现,此方法是在执行阶段执行的;如果闭包返回true,则认为该task时最新的;

    如果2次执行 gradle makeReleaseVersion 后一次gradle知道项目版本被设置为发布版本了,并自动跳过第二次执行;

    UP-TO-DATE

    使用自定义Task

    自定义Task包含2个组件:自定义的task类(封装逻辑行为)与真实的task(提供用于配置行为的task类所暴露的属性值);gradle称之为增强型task;
    可维护性是编写自定义task类的优势之一,毕竟操作的是一个实际的类;

    编写自定义Task

    继承自 DefaultTask,使用注解表示输入输出;

    class ReleaseVersionTask extends DefaultTask {
    
        @Input Boolean release      // 注解声明输入属性 release
        @OutputFile File destFile   // 定义输出文件
    
        ReleaseVersionTask() {
            group = 'versioning'
            description = 'Makes project a realase version.'
        }
    
     // 使用注解声明将被执行的方法 ,
     // 加上这个action的作用是当执行这个task的时候会自动执行这个方法
        @TaskAction    
        void start() {
            project.version.release = true
            ant.propertyfile(file: versionFile) {
                entry(key: 'release', type: 'string', operation: '=', value: 'true')
            }
        }
    }
    

    使用自定义Task
    在构建脚本中,创建一个ReleaseVersionTask类型的task,并且通过它的属性赋值来设置输入和输出,如:

    // 增強型task暴露的属性单独赋值
    task makeReleaseVersion2(type: ReleaseVersionTask) {
        release = version.relase
        destFile = versionFile
    }
    

    task规则

    某些时候,编写的task可能做着相同的事情,如下版本的管理task任务:

    // 主版本+1
    task incrementMajorVersion(group: 'versioning', description: 'Increments project major version.') << {
        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)
        }
    }
    
    // 次版本+1
    task incrementMinorVersion(group: 'versioning', description: 'Increments project major version.') << {
        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)
        }
    }
    

    分别运行命令:gradle incrementMinorVersion -i ,可以看到输出;
    但上面的是否可以改进呢?

    task规则的命名模式

    根据task名称模式执行特定的逻辑,模式由2部分组成:
    task名称的静态部分 与 一个占位符,他们联合起来组成一个命令;
    如上面的,像:increment<Classifier>Version

    Gralde的一些核心插件,利用了task规则,如:clean<TaskName>;

    声明task规则

    首先需要获得对TaskContainer的引用,然后可调用其addRule(String, Closure) 方法,通过project的getTasks()方法获取 TaskContainer引用;
    使用task规则,实现上线动作:

    // 使用 taskContainer 添加规则
    tasks.addRule("Pattern: increment<Classifier>Version - Increments the project version.") {
        String taskName ->
            if (taskName.startsWith('increment') && taskName.endsWith('Version')) {
                task(taskName) << {  // 符合命名模式的task动态添加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会列出一个具体的tasks组rules:

    tasks Rules 组

    task 规则不能分组,其显示在 Rules组下;

    我们运行命令:gradle incrementMajorVersion -i,输出如下:

    执行task的分组任务的输出

    在buildSrc目录下构建代码

    可以将构建脚本的一些groovy类:如上面的ProjectVersion和自定义的task ReleaseVersionTask,这些适合移动到项目的 buildSrc目录下;
    Gradle在buildSrc目录下使源文件结构标准化,Java代码在 src/main/java,Groovy代码放在src/main/groovy目录下;这些目录下代码会自动编译,并添加到Gradle构建脚本的classpath中;

    Android Studio 中在项目的根目录,建立buildSrc文件夹,并sync一下工程,然后新增文件夹路径 src/main/groovy/包名, 就可以创建groovy脚本了;

    buildSrc项目出来了

    在当前 module下对应的 build.gradle文件,直接引入 buildSrc 下的类,这样Task定义与build.gradle分离了;

    import com.better.ProjectVersion2
    import com.better.ReleaseVersionTask2

    总结:

    了解一下Task的工作细节、自定义Task,也了解一些Task配置与Task动作之间的区别;最后我们在buildSrc中,实现自定义Task的分离,将task独立出来;

    相关文章

      网友评论

          本文标题:Gradle实战读书笔记之一 Gradle 构建块

          本文链接:https://www.haomeiwen.com/subject/ulpddxtx.html