• 开发效率优化之自动化构建系统Gradle(二)下篇


    阿里P7移动互联网架构师进阶视频(每日更新中)免费学习请点击:https://space.bilibili.com/474380680
    本篇文章将继续从自定义 Gradle 插件开发来介绍自动化构建系统Gradle:

    Gradle 插件简介

    Gradle 插件是一个能够将 Gradle 的构建逻辑(build logic)和构建任务(build task)打包到一起,以便在多个项目的构建脚本(build.gradle)中应用(apply)的工具。

    例如,build.gradle 构建脚本文件内 apply plugin: 'java'apply plugin: 'com.android.application' 中的 javacom.android.application 就是官方提供的 Gradle 插件,通过应用这些插件,可以丰富项目的构建任务与构建逻辑。

    除官方提供的插件外,Gradle 还允许开发者定义自己的 Gradle 插件。开发者可以根据实际需求定义自己的构建逻辑和构建任务,将其打包为 Gradle 插件,从而在多个项目的构建脚本中复用。此外,还可以将自定义的 Gradle 插件发布到 plugin portal或其他仓库中,更为方便的分享给他人使用。

    Gradle 插件对编程语言没有太多限制,只要是能够被编译为 JVM 字节码的编程语言,都能用来编写 Gradle 插件。Gradle-API 的被设计为对 Groovy、Java、Koltin 友好的,通常情况下,使用 Java 或 Kotlin 这类静态类型语言实现的 Gradle 插件的性能要比使用 Groovy 实现的相同常见的性能更好。

    开始之前

    Gradle 作为一个普通的构建工具,本身并不依赖任何可视化 GUI 工具。为简化步骤,本文将采用命令行方式来完成自定义 Gradle 插件的演示。在开始之前,需先将 gradle 命令添加到系统环境变量中。

    若读者在 IDEA / Android Studio 使用过 gradle ,则可在当前用户目录下找到 gradle 命令,具体路径如下

    • Mac:/Users/当前用户名/.gradle/wrapper/dists/gradle版本/沙盒路径/gradle版本/bin
    • Win:C:Users当前用户名.gradlewrapperdistsgradle版本沙盒路径gradle版本in

    若读者的电脑中尚未安装 gradle,则可在 Gradle 官方 下载安装。

    以笔者使用的环境为例,gradle 命令所在目录为

    ~/.gradle/wrapper/dists/gradle-5.4.1-all/3221gyojl5jsh0helicew7rwx/gradle-5.4.1/bin
    

    将 gradle 命令所在目录添加到环境变量中

    • Mac:在 ~/.bashrc 中添加
    export PATH=~/.gradle/wrapper/dists/gradle-5.4.1-all/3221gyojl5jsh0helicew7rwx/gradle-5.4.1/bin:$PATH
    
    • Win:在系统环境变量中添加
    C:Users用户名.gradlewrapperdistsgradle-5.4.1-all805usxkvhgx6e1wbo8o64g0txgradle-5.6.1in
    

    牛刀小试

    在命令行中输入

    gradle --version
    

    得到如下输出则表示 gradle 环境设置成功

    ------------------------------------------------------------
    Gradle 5.4.1
    ------------------------------------------------------------
    
    Build time:   2019-04-26 08:14:42 UTC
    Revision:     261d171646b36a6a28d5a19a69676cd098a4c19d
    
    Kotlin:       1.3.21
    Groovy:       2.5.4
    Ant:          Apache Ant(TM) version 1.9.13 compiled on July 10 2018
    JVM:          1.8.0_171 (Oracle Corporation 25.171-b11)
    OS:           Mac OS X 10.14.6 x86_64
    

    自定义 Gradle 插件

    自定义 gradle 插件可以在以下三个地方创建,分别是:

    1. 构建脚本内
    2. buildSrc 模块内
    3. 单独项目

    构建脚本内建方式

    build.gradle 内直接创建 Gradle 插件

    优点:

    • build.gradle 中创建的插件将<u>被自动编译并包含在 classpath 中</u>,使用时无需在构建脚本内指定 classpath

    缺点:

    • 此插件仅在当前构建脚本中有效,对外部文件不可见,无法在当前构建脚本以外的其他地方复用此插件

    示例

    1. 创建 BuildInDemo 目录

    mkdir BuildInDemo
    

    2. 在 BuildInDemo 目录内中新建 build.gradle 文件

    cd BuildInDemo
    touch build.gradle
    

    使用 tree 命令查看创建后的目录结构,如下所示

    BuildInDemo
    .
    └── build.gradle
    
    0 directories, 1 file
    

    3. 在 BuildInDemo/build.gradle 内创建并应用 Gradle 插件,代码如下

    // 1. 创建插件 BuildInPlugin
    class BuildInPlugin implements Plugin<Project> {
        // 2. 应用插件时执行此函数
        @Override void apply(Project target) {
            println("hello form build-in plugin")
        }
    }
    //3. 应用插件
    apply plugin: BuildInPlugin
     // 2. 应用插件时执行此函数
     @Override void apply(Project target) {
     println("hello form build-in plugin")
     }
    }
    //3. 应用插件
    apply plugin: BuildInPlugin
    

    4. 构建此 build.gradle 文件

    gradle build
    

    Gradle 构建时将执行 build.gradle 中的代码,当执行到 apply plugin: BuildInPlugin 时,将会调用 BuildInPlugin 的实例方法 apply(Project p)。因此在构建过程中,可以看到如下输出,其中第 2 行即为上一步自定义插件中打印的内容,表明插件应用成功

    > Configure project :
    hello form build-in plugin
    
    > Task :buildEnvironment
    
    ------------------------------------------------------------
    Root project
    ------------------------------------------------------------
    
    classpath
    No dependencies
    
    A web-based, searchable dependency report is available by adding the --scan option.
    
    BUILD SUCCESSFUL in 0s
    1 actionable task: 1 executed
    

    buildSrc 模块方式

    rootProject/buildSrc 文件夹是 Gradle 的预留目录,用来存放当前项目私有 Gradle 插件的源代码与构建脚本

    优点:

    • 项目构建时,Gradle 会自动编译项目目录下的 buildSrc 文件夹下的构建脚本和源码,并将其添加到项目构建脚本的 classpath 中,因此在使用 buildSrc 中创建的插件时,无需再手动指定 classpath
    • buildSrc 文件夹中构建脚本和 Gradle 插件同一项目均可见,因此同一项目中的其他模块也可以使用 buildSrc 中创建的插件

    缺点:

    • 此处创建的插件对外部项目不可见,无法在其他项目中复用

    示例

    1. 创建 PluginBuildSrcDemo 项目模块

    创建 PluginBuildSrcDemo 目录,并在该目录下创建 build.gradle 文件

    mkdir PluginBuildSrcDemo && cd PluginBuildSrcDemo && touch build.gradle
    

    2. 创建 buildSrc 子模块

    2.1 在 PluginBuildSrcDemo 目录下创建 buildSrc 目录

    mkdir buildSrc
    

    2.2 在 PluginBuildSrcDemo/buildSrc 目录下创建 buildSrc 子模块的构建脚本文件 build.gradle

    cd buildSrc
    touch build.gradle
    

    PluginBuildSrcDemo/buildSrc/build.gradle 的内容如下

    // 使用 plugins 块语法应用插件
    plugins {
      // 应用 kotlin 插件
      id 'org.jetbrains.kotlin.jvm' version '1.3.50'
    }
    dependencies {
      // 仅在编译时使用 Grdale-API 依赖
      compileOnly gradleApi()
      // 在插件源码中添加 kotlin 标准库依赖
      implementation 'org.jetbrains.kotlin:kotlin-stdlib'
    }
    

    2.3 创建 buildSrc 模块的源码目录

    cd PluginBuildSrcDemo/buildSrc
    mkdir -p /src/main/kotlin/com/example/plugin
    

    2.4 创建插件文件 PluginBuildSrc.kt

    cd PluginBuildSrcDemo/buildSrc/src/main/kotlin/com/example/plugin
    touch PluginBuildSrc.kt
    

    PluginBuildSrc.kt 的代码如下

    package com.example.plugin
    
    import org.gradle.api.Plugin
    import org.gradle.api.Project
    
    class PluginBuildSrc : Plugin<Project> {
      override fun apply(target: Project) {
        println("hello from buildSrc plugin")
      }
    }
    

    2.5 声明 Gradle 插件的 ID 与实现类

    此步骤是可选的:若使用插件 ID 形式应用自定义插件,则必须进行此步骤;若使用插件实现类的形式应用自定义插件,则可跳过此步骤。

    完成此步骤的方式有两种,任选其一即可

    方式 1. META-INF 方式

    创建 PluginBuildSrcDemo/buildSrc/src/main/resources/META-INF/gradle-plugins 目录

    cd PluginBuildSrcDemo/buildSrc
    mkdir -p src/main/resources/META-INF/gradle-plugins
    

    gradle-plugins 目录下创建 com.example.plugin.properties 属性文件,红色部分表示插件的 ID

    cd src/main/resources/META-INF/gradle-plugins
    touch com.example.plugin.properties
    

    属性文件的内容如下,表示插件 ID 为 com.example.plugin 的插件所对应的实现类为 com.example.plugin.PluginBuildSrc

    implementations-class=com.example.plugin.PluginBuildSrc
    

    方式 2. java-gradle-plugin 插件方式

    java-gradle-plugin 是一个用于开发 Gradle 插件的辅助插件,它内置了很多辅助功能:

    1. 为宿主模块添加 gradlePlugin 配置块,可在此处配置插件的 ID 和实现类
    2. 为宿主模块添加 complile gradleApi() 依赖
    3. etc…

    此处我们主要使用 gradlePlugin 配置块代替 META-INF 目录下的属性文件。java-gradle-plugin 的使用方式非常简单,只需在 PluginBuildSrcDemo/buildSrc/build.gradle 构建脚本文件中简单配置即可,如下所示。

     plugins {
          id 'org.jetbrains.kotlin.jvm' version '1.3.50'
    + //1. 应用 java-gradle-plugin 插件
    +     id 'java-gradle-plugin' 
      }
         
      dependencies {
    -     compileOnly gradleApi() // java-gradle-plugin 插件已为宿主添加 gradleApi 依赖,此行可移除 
          implementation 'org.jetbrains.kotlin:kotlin-stdlib'
      }
    + //2. 添加 gradlePlugin 配置块信息
    + gradlePlugin {
    +     plugins{
    +         // 此处的 tag 可以为任意名称
    +         tag1{
    +             id = 'com.example.plugin.buildsrc' //自定义插件的 ID
    +             implementationClass  = 'com.example.plugin.PluginBuildSrc' //自定义插件的实现类
    +         }
    +     }
    + }
    

    此时在 PluginBuildSrcDemo 目录下使用 tree 命令,可以看到当前的目录结构如下

    PluginBuildSrcDemo
    .
    ├── build.gradle
    ├── buildSrc
    │   ├── build.gradle
    │   └── src
    │       └── main
    │           └── kotlin
    │               └── com
    │                   └── example
    │                       └── plugin
    │                           └── PluginBuildSrc.kt
    7 directories, 3 files
    

    3. 在 PruginBuildSrcDemo 项目模块中应用 buildSrc 中声明的 Gradle 插件

    PluginBuildSrcDemo/build.gradle 构建脚本文件中添加如下代码

    //apply plugin: '插件 ID' 
    apply plugin: 'com.example.plugin'
    //apply plugin: 实现类
    //apply plugin: com.example.plugin.PluginBuildSrc
    

    应用插件时,指定插件 ID 或指定插件的实现类都可以,但指定插件 ID 的形式更为简短及灵活,在实际开发中也更为常见。

    PluginBuildSrcDemo 目录下执行 gradle build 命令进行构建,可看到如下输出

    > Configure project :
    hello from buildSrc plugin
    
    > Task :buildEnvironment
    ------------------------------------------------------------
    Root project
    ------------------------------------------------------------
    classpath
    No dependencies
    
    A web-based, searchable dependency report is available by adding the --scan option.
    
    BUILD SUCCESSFUL in 3s
    1 actionable task: 1 executed
    

    其中第 2 行是我们在 buildSrc 模块中定义的 Gradle 插件所打印日志,表明 buildSrc 模块 中的自定义插件在根项目中已生效。

    4. 创建 SubModule 子模块

    buildSrc 模块中定义的插件可以在同一项目的任意模块的构建脚本中使用,接下来便演示在 SubModule 子模块中的应用。

    4.1 创建 SubModule 目录与构建脚本

    // 1. 在 PluginBuildSrcDemo 目录下创建 SubModule 目录
    cd PluginBuildSrcDemo && mkdir SubModule
    // 2. 在 SubModule 目录下新建 gradle 构建脚本 
    cd SubModule
    touch build.gralde
    

    PluginBuildSrcDemo/SubModule/build.gradle 的内容如下

    apply plugin: 'com.example.plugin'
    

    4.2 将 SubModule 模块关联到 PluginBuildSrcDemo 项目中

    PluginBuildSrcDemo 目录下新建 settings.gradle 文件,内容如下

    // 将SubModule 子模块添加到 PluginBuildSrcDemo 项目模块中
    include ':SubModule'
    

    此时在 PluginBuildSrcDemo 目录下使用 tree 命令,可以看到项目当前的目录结构如下

    PluginBuildSrcDemo
    .
    ├── SubModule
    │   └── build.gradle
    ├── build.gradle
    ├── buildSrc
    │   ├── build.gradle
    │   └── src
    │       └── main
    │           └── kotlin
    │               └── com
    │                   └── example
    │                       └── plugin
    │                           └── PluginBuildSrc.kt
    └── settings.gradle
    

    5. 在 SubModule 模块执行构建命令

    cd PluginBuildSrcDemo/SubModule
    gradle build
    

    得到如下输出

    > Configure project :SubModule
    hello from buildSrc plugin
    
    > Task :SubModule:buildEnvironment
    
    ------------------------------------------------------------
    Project :SubModule
    ------------------------------------------------------------
    
    classpath
    No dependencies
    
    A web-based, searchable dependency report is available by adding the --scan option.
    
    BUILD SUCCESSFUL in 0s
    1 actionable task: 1 executed
    

    独立项目方式

    采用 buildSrc 模块方式时,Gradle 会妥善处理 buildSrc 模块的构建脚本与源码,并将其添加到当前项目的 classpath 中。但 buildSrc 方式的插件只能在项目内共享与复用,若要在其他项目中使用该插件,还需要再进行下列操作

    • 将插件发布到 maven 仓库(任意仓库)
    • 在需要应用该插件的构建脚本中的 repository 部分添加该插件所在的 maven 仓库
    • 在需要应用该插件的构建脚本中的 classpath 部分添加该插件对应的 maven 坐标 (group : id : version)

    因为是在其他项目中使用该项目 buildSrc 模块 中的自定义 Gradle 插件,所以 Gradle 的 buildSrc 保留目录优势不再。如果将模块名由 buildSrc 修改为其他名称,则可将其称为独立的 Gradle 插件模块。

    在本小节中,我们主要学习 gradle 插件的发布与使用。

    1. 创建 gradle 插件项目

    buildSrc 方式相同,先创建构建脚本与自定义插件类

    mkdir StandaloneDemo 
    cd StandaloneDemo
    touch build.gradle
    mkdir src/main/kotlin/com/example/plugin
    touch src/main/kotlin/com/example/plugin/StandalonePlugin.kt
    

    StandaloneDemo/build.gradle 的内容如下

    plugins {
        id 'org.jetbrains.kotlin.jvm' version '1.3.50'
        id 'java-gradle-plugin'
    }
    
    gradlePlugin{
        plugins{
            sometag{
                id = 'com.example.plugin'
                implementationClass = 'com.example.plugin.StandalonePlugin'
            }
        }
    }
    

    StandaloneDemo/src/main/kotlin/com/example/plugin/StandalonePlugin.kt 的内容如下

    package com.example.plugin
    
    import org.gradle.api.Plugin
    import org.gradle.api.Project
    
    class StandalonePlugin : Plugin<Project> {
        override fun apply(target: Project) {
            println("hello from standalone plugin")
        }
    }
    

    2. 将 gradle 插件发布到 maven 仓库

    2.1 在 StandaloneDemo/build.gradle 文件中配置发布信息

    使用 maven-publish 插件将自定义插件发布到本地 maven 仓库中,如下所示

    plugins {
         id 'org.jetbrains.kotlin.jvm' version '1.3.50'
         id 'java-gradle-plugin'
    +    //1. 应用 maven-publish 插件
    +    id 'maven-publish'
     }
     
     gradlePlugin{
         plugins{
             sometag{
                 id = 'com.example.plugin'
                 implementationClass = 'com.example.plugin.StandalonePlugin'
             }
         }
     }
    +//2. 设置发布相关配置
    +publishing {
    +    publications {
    +        //3. 将插件发布到 maven 仓库
    +        maven(MavenPublication) {
    +                       //4. 设置插件的 maven 坐标
    +            groupId 'com.example'//组织 ID
    +            artifactId 'plugin'  //制品 ID
    +            version 'snapshot'   //制品版本
    +            from components.kotlin
    +        }
    +    }
    +   //5. 设置发布仓库
    +   repositories {
    +       // 6. 发布到本地 maven 仓库 
    +       mavenLocal()
    +   }
    +}
    

    2.2 使用 publishMavenPublicationToMavenLocal 任务将插件发布到本地仓库

    cd StandaloneDemo
    gradle publishMavenPublicationToMavenLocal
    

    执行以上命令后,gradle 会把 StandaloneDemo 中的代码编译打包后发布到本地 maven 仓库中,本地 maven 仓库通常存放于当前用户目录下,具体路径为

    • Mac: ~/.m2/repository
    • Win:C:Users当前用户名.m2 epository

    我们可以在此目录下看到刚刚发布的插件,如下图所示

     
    19956127-e64026bf96590e3c.png
     

    3. 在其他项目中使用本地 maven 仓库中的 gradle 插件

    3.1 新建 OtherProject 项目

    mkdir OtherProject
    cd OtherProject
    touch build.gradle
    

    build.gradle 的文件内容如下,相比 buildSrc 方式,应用时多了 11 行的 buildscript 相关的配置信息。

    +buildscript {
    +  repositories {
    +    //1. 为当前构建脚本添加插件所在的 maven 仓库,本例中为 maven 本地仓库
    +    mavenLocal()
    +  }
    +  dependencies {
    +    //2. 为当前构建脚本添加如下依赖
    +    //`com.exaple`、`plugin`、`snapshot` 是在上一步中设置的 maven 三维坐标
    +    classpath 'com.example:plugin:snapshot'
    +  }
    +}
     //3. 应用独立插件项目中的自定义插件
     // apply plugin: '插件 ID'
     apply plugin: 'com.example.plugin'
    

    3.2 构建 OtherProject 项目

    cd OtherProject
    gradle build
    

    输入以上命令,得到如下输出

    > Configure project :
    hello from standalone plugin
    
    > Task :buildEnvironment
    
    ------------------------------------------------------------
    Root project
    ------------------------------------------------------------
    
    classpath
    --- com.example:plugin:snapshot
    
    A web-based, searchable dependency report is available by adding the --scan option.
    
    BUILD SUCCESSFUL in 0s
    1 actionable task: 1 executed
    

    小结

    本文演示了 gradle 插件的三种创建方式,相关代码已上传至 github 中,点此即可查看。行文匆忙难免疏忽,如有遗漏还请斧正。

  • 相关阅读:
    luogu P5473 [NOI2019]I 君的探险 交互 随机 二分 分治 整体二分
    218. The Skyline Problem
    315. Count of Smaller Numbers After Self
    493. Reverse Pairs
    307. Range Sum Query
    1409. Queries on a Permutation With Key
    如果redis没有设置expire,他是否默认永不过期?
    同步调用和异步调用
    缓存穿透、缓存击穿、缓存雪崩概念及解决方案
    Python实现发送邮件---转载至https://www.cnblogs.com/liuqingzheng/articles/10072695.html
  • 原文地址:https://www.cnblogs.com/Android-Alvin/p/11962997.html
Copyright © 2020-2023  润新知