Gradle系列5--自定义Gradle插件

探讨Gradle插件的自定义有助更好的理解Gradle,本文综合了userguide中的几篇相关文档,将不单独给出链接(除非很有必要),主要有:

Gradle插件中创建可重用构建逻辑的过程,然后可以将其应用于其他Gradle构建。Gradle的核心提供了构建任何东西的基础架构,但插件允许构建脚本作者以最少的努力完成工作,使他们能够专注于构建的内容,而不是如何构建。插件可以应用约定,添加新任务类型,与第三方工具和库集成等。例如,java插件提供标准的源目录布局和任务,例如compileJava和compileTestJava。

Gradle插件打包可重用的构建逻辑片段,可用于许多不同的项目和构建。Gradle允许自定义插件,可以重用构建逻辑,并与其他人分享。可以使用任何语言实现Gradle插件,前提是实现最终能编译为字节码。示例中,我们将使用Groovy作为实现语言。Groovy,Java或Kotlin都是用于实现插件的语言的不错选择,因为Gradle API已经设计为可以很好地使用这些语言。通常,使用Java或Kotlin实现的静态类型的插件将比使用Groovy实现的相同插件执行得更好。

打包插件

可以放置Gradle插件代码的几个地方:

  • 构建脚本
    直接在build.gradle中包含代码, 自动编译. 对外不可见,不可重用.

  • buildSrc 工程
    插件代码放在 rootProjectDir/buildSrc/src/main/groovy 目录. Gradle 编译、测试插件,并添加到在build.gradle的classpath中. 对每个构建脚本可见. 对外不可见,不可重用.

  • 独立工程
    独立的插件程序, 生成发布一个可供多个构建共享的JAR. 一般 JAR可以包含一些插件或任务.

简单插件

要创建Gradle插件,需要编写一个实现Plugin接口的类。当插件应用于项目时,Gradle会创建插件类的实例并调用实例的Plugin.apply()方法。项目对象作为参数传递,插件可以使用它来自由配置项目。以下示例包含greeting插件,该插件向项目添加hello任务。

build.gradle

class GreetingPlugin implements Plugin<Project> {
    void apply(Project project) {
        project.task('hello') {
            doLast {
                println 'Hello from the GreetingPlugin'
            }
        }
    }
}

// Apply the plugin
apply plugin: GreetingPlugin

gradle -q hello输出:

> gradle -q hello
Hello from the GreetingPlugin

需要注意的一点是,应用它的每个项目都会创建一个新的插件实例。Plugin是泛型类型,示例使其接收Project类型作为类型参数。插件可以改为接收类型为应用于设置脚本中的Settings或初始化脚本中的Gradle类型的参数。

插件可配置

大多数插件需要从构建脚本中获取一些配置,一种作法是使用扩展对象。Gradle的Project关联一个包含已应用于项目的插件的所有设置和属性ExtensionContainer对象,通过向该容器添加扩展对象来为插件提供配置。扩展对象只是一个Java Bean兼容类。Groovy是实现扩展对象的很好选择,因为普通的旧Groovy对象包含Java Bean所需的所有getter和setter方法,Java和Kotlin也是不错的选择。让我们为项目添加一个简单的扩展对象。这里添加一个可以配置问候语的 greeting扩展对象。

build.gradle

class GreetingPluginExtension {
    String message = 'Hello from GreetingPlugin'
}

class GreetingPlugin implements Plugin<Project> {
    void apply(Project project) {
        // 添加 'greeting' 扩展对象
        def extension = project.extensions.create('greeting', GreetingPluginExtension)
        // 添加一个使用扩展对象配置的task
        project.task('hello') {
            doLast {
                println extension.message
            }
        }
    }
}

apply plugin: GreetingPlugin

// 配置扩展
greeting.message = 'Hi from Gradle'

gradle -q hello输出

> gradle -q hello
Hi from Gradle

示例中,GreetingPluginExtension是一个旧的Groovy对象,有一个message的属性。扩展对象被添加到greeting的插件列表中,而后该对象成为项目的一个与扩展对象同名的有效属性。以上面为例,Project中添加一个greeting扩展就相当于给Project配置了一个greeting属性。通常需要在单个插件上指定几个相关属性。Gradle为每个扩展对象添加了一个配置闭包块(Closure block),因此您可以将设置组合在一起。以下示例显示了它的工作原理。

build.gradle

class GreetingPluginExtension {
  String message
  String greeter
}

...
        println "${extension.message} from ${extension.greeter}"
...

apply plugin: GreetingPlugin

// 使用DSL块配置扩展
greeting {
    message = 'Hi'
    greeter = 'Gradle'
}

gradle -q hello输出

> gradle -q hello
Hi from Gradle

这里在greeting闭包内将多个设置组合在一起。构建脚本中闭包块名称(greeting)需要与扩展对象名称匹配。当闭包执行时,扩展对象上的字段将根据标准Groovy闭包委托功能映射到闭包内的变量。

buildSrc工程

复杂的构建逻辑通常封装为自定义任务或二进制插件。自定义任务和插件实现不应该存在于构建脚本中。只要代码不需要在多个独立项目之间共享,就可以非常方便地使用buildSrc实现。目录buildSrc被视为已包含的构建。一旦发现该目录,Gradle会自动编译并测试代码并将其放入构建脚本的classpath中。对于多模块构建,只能有一个buildSrc目录,且必须位于根项目目录下。buildSrc应该优先于脚本插件,因为它更容易维护,重构和测试代码。buildSrc适用于Java和Groovy项目的相同源代码约定,还提供对Gradle API的直接访问。可以在buildSrc下的build.gradle中声明其他依赖项。

$ mkdir gradle-plugin-buildsrc
$ cd gradle-plugin-buildsrc/
$ gradle wrapper --gradle-version 4.6 --distribution-type all #gradle-4.6-all初始化Wrapper
$ ./gradlew init    #初始化根工程
$ mkdir buildSrc    
$ cd buildSrc/
$ ../gradlew init --type groovy-library #buildSrc初始化为groovy-library
$ rm -r gradle* sett* src/*/*/*.groovy  #删除无用文件

添加buildSrc/src/main/groovy/org/example/greeting/GreetingPlugin.groovy

package org.example.greeting

import org.gradle.api.Plugin
import org.gradle.api.Project

class GreetingPlugin implements Plugin<Project> {
    @Override
    void apply(Project project) {
        project.tasks.create("hello", Greeting.class) { task ->
            task.setMessage("Hello")
            task.setRecipient("World")
        }
    }
}

添加buildSrc/src/main/groovy/org/example/greeting/Greeting.groovy

package org.example.greeting

import org.gradle.api.DefaultTask
import org.gradle.api.tasks.TaskAction

class Greeting extends DefaultTask {
    private String message
    private String recipient

    String getMessage() { return message }

    void setMessage(String message) { this.message = message }

    String getRecipient() { return recipient }

    void setRecipient(String recipient) { this.recipient = recipient }

    @TaskAction
    void sayGreeting() {
        println "$message, $recipient!"
    }
}

根工程build.gradle加入

apply plugin: org.example.greeting.GreetingPlugin

后执行

$ ./gradlew hello
Hello, World!

我这里执行的时候抛出了一个buildSrc下的异常:

Caused by: groovy.lang.GroovyRuntimeException: Conflicting module versions. Module [groovy-all is loaded in version 2.4.12 and you are trying to load version 2.4.13

groovy-all版本改成2.4.12就可以了

另外由于buildSrc工程能被构建任务自动识别, 因此build.gradle中的内容完全可以删除,只保留额外的依赖和配置, 这样也不会有上面版本冲突的问题

分配插件id

大多数情况使用id以其容易记忆会比使用全类名来的方便,还会使构建文件更整洁。下面声明插件的id。
创建buildSrc/src/main/resources/META-INF/gradle-plugins/org.example.greeting.properties

implementation-class=org.example.greeting.GreetingPlugin

Gradle使用这个文件来确定哪个类实现了Plugin接口。文件名去掉.properties后缀就是插件的id。根工程build.gradle改成

apply plugin: 'org.example.greeting'

plugins {
    id 'org.example.greeting'
}

注意上面是如何将org.example.greeting.properties文件映射到插件id的。

独立工程

现在将我们的插件移到一个独立的项目中,可以发布它并与其他人分享。这是一个Groovy项目,它生成一个包含插件类的JAR。这是项目的简单构建脚本。它应用Groovy插件,并将Gradle API添加为编译时依赖项。

$ mv buildSrc plugin

此时plugin模块不再享有buildSrc时也有的自动被构建脚本识别、自动添加gradle/groovy等依赖、自动添加到classpath中这些“特权”, 因此这些工作需要插件作者来做。编辑plugin/build.gradle:

  1. 识别工程

    顶级settings.gradle加入include ':plugin'

  2. 添加依赖

    plugins {
        id 'groovy'
    }
    
    dependencies {
        compile gradleApi()
        compile localGroovy()
    }
    
  3. 分配一个id

    如果之前buildSrc中没有分配id这里的作法和buildSrc分配插件id是一样的, 这里重新进行配置。
    创建 plugin/src/main/resources/META-INF/gradle-plugins/org.samples.greeting.properties

    implementation-class=org.example.greeting.GreetingPlugin
    

    到这里还不能使用插件。顶级build.gradle要到哪里去加载插件jar包呢? 对了classpath没有配置,还要继续配置。

  4. 发布插件
    发布插件的方式常见的有maven、ivy和本地这三种仓库, 前两个属于中央仓库,提交产品需要审核,这里采用便捷的本地maven仓库。编辑plugin/build.gradle加入

    plugins {
      id 'maven'
    }
    
    uploadArchives{
        repositories.mavenDeployer{
            repository(url:uri('../repo')) //保存到上一级目录的repo下
            pom.project{
                groupId 'org.gradle'
                artifactId 'customPlugin'
                version '1.0'
            }
        }
    }
    
  5. 注释掉顶级build.gradle下应用的插件, 根目录执行./gradlew uploadArchives

    执行完毕后工程顶级目录下有个repo目录:

    gradle-plugin-buildsrc/
    ├── build.gradle
    ...
    ├── plugin
    ├── repo
    └── settings.gradle
    
    repo/
    └── org
        └── gradle
            └── customPlugin
                ├── 1.0
                │   ├── customPlugin-1.0.jar
                │   ├── customPlugin-1.0.jar.md5
                │   ├── customPlugin-1.0.jar.sha1
                │   ├── customPlugin-1.0.pom
                │   ├── customPlugin-1.0.pom.md5
                │   └── customPlugin-1.0.pom.sha1
                ├── maven-metadata.xml
                ├── maven-metadata.xml.md5
                └── maven-metadata.xml.sha1
    
  6. 添加插件成品到顶级build.gradle的classpath中

    buildscript {
        repositories {
            maven {
                url uri('repo')
            }
        }
        
        dependencies{
            classpath 'org.gradle:customPlugin:1.0'
        }
    }
    
  7. 新建一个模块来应用插件

    $ mkdir test-plugin
    

    顶级build.gradle加入一行include ':test-plugin'
    test-plugin/build.gradle加入:apply plugin:'org.samples.greeting'
    执行:./gradlew hello

    $ ./gradlew hello
    Hello, World!
    

    原文作者:cntlb
    原文地址: https://www.jianshu.com/p/208501775eff
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞