当前位置: 移动技术网 > 移动技术>移动开发>Android > Gradle 自定义插件

Gradle 自定义插件

2019年10月25日  | 移动技术网移动技术  | 我要评论

思维导图

使用版本 5.6.2

插件被用来封装构建逻辑和一些通用配置。将可重复使用的构建逻辑和默认约定封装到插件里,以便于其他项目使用。

你可以使用你喜欢的语言开发插件,但是最终是要编译成字节码在 jvm 运行的。

gradle 有两种插件,脚本插件和二进制插件。

关于插件的介绍,可以参考我的另一篇文章 gradle 插件

这里讲的自定义插件是二进制插件,二进制插件可以打包发布,有利于分享。

可以在三个地方定义插件

  • 在脚本里
  • 在 buildsrc 下
  • 在单独的项目里

三个地方的插件的用途目的不同。

在脚本里的插件

其他项目无法使用,只能在本脚本里使用。

在 buildsrc 下

在项目的 buildsrc 目录下的插件,这个项目里的所有(子)项目都可以使用。

在单独的项目里

你可以为你的插件创建一个项目,这个项目可以打包发布 jar,提供给其他任何项目使用。

创建一个插件

建议使用静态语言,例如 java ,kotlin,开发工具建议使用 intellij idea 。

一个插件就是个实现了 plugin 的类。

当插件被应用到项目时,gradle 会实例化这个插件并调用 plugin.apply() 方法,并将这个项目的实例当做参数传递进去。插件就可以对这个项目进行各种配置了。

customplugin.java

// 定义一个插件
class customplugin implements plugin<project>{

    @override
    void apply(project target) {
        // do something
    }
}

前面说到可以在三个地方创建插件,现在来一一实现下。

在脚本里创建一个插件

可以在 build.gradle 脚本里任意地方定义。

build.gradle

// 定义一个插件
class customplugin implements plugin<project>{

    @override
    void apply(project target) {
      //添加一个任务
     target.task('hello', group: 'util') {
         dolast {
             logger.quiet("hello plugin.")
         }
     }
    }
}

//直接在脚本里应用
apply plugin:customplugin

在 gradle 窗口就可以看到应用插件后的添加的任务
添加的任务

双击任务或者命令行输入都可以执行 hello 任务

gradle hello

在项目的 buildsrc 目录下创建项目

这里使用的是 groovy 。

在这个目录下创建项目会被 gradle 自动识别的。

结构如下

buildsrc 目录结构

  1. 在项目根目录下创建目录 buildsrc
  2. 在 buildsrc 下按照 java 工程或者 groovy 工程(这取决于你用什么语言)新建目录

$projectdir/buildsrc/src/main/groovy

  1. 在 groovy 创建你的包 (可能现在还不能被识别为项目,那就创建目录),例如 com.github.skymxc
  2. 在包里创建插件,也就是创建一个实现了 plugin 的类。

这里做简单的示范:

在插件里为 jar 任务添加一个操作:生成记录文件

jarlogplugin.groovy

/**
 * 输出 生成记录到指定文件
 */
class jarlogplugin implements plugin<project> {
    @override
    void apply(project target) {
        //增加一个扩展配置用来接收参数
        target.extensions.create("log", logextension)

        //添加一个任务
        target.task(type: jar,group:'util','jarwithlog',{
            dolast {
                //使用配置
                def file = target.log.outputpath;
                if (file==null){
                    file = new file(target.projectdir,"/log/jarlog.txt").getpath()
                }
                println "存储目录是 ${file}"
                def content = "${getarchivefilename().get()}---${getnow()}\n"
                writefile(file,content)
            }
        })

        //为 jar 任务添加一个 操作,
        target.tasks.jar.dolast {
            println "当前时间是 ${getnow()},打了一个 jar-> ${version}"
            //存到指定文件记录
            def file = new file(target.projectdir,"/log/jarlog.txt");
            def content = "${version}---${getnow()}\n"
            writefile(file.getabsolutepath(),content)
        }
    }

    def string getnow(){
        def dateformat = new simpledateformat("yyyy-mm-dd hh:mm:ss.sss");
        return dateformat.format(calendar.getinstance().gettime());
    }

    def void writefile(string path,string content){
        def file = new file(path);
        if (!file.exists()){
            if (!file.getparentfile().exists()){
                file.getparentfile().mkdirs();
            }
            file.createnewfile();
        }
        filewriter writer = new filewriter(file.getabsolutepath(),true);
        bufferedwriter bufferedwriter = new bufferedwriter(writer);
        bufferedwriter.write(content);
        bufferedwriter.close();
    }
}

配置 dsl

上面使用了一个扩展来接收参数, 普通的对象就可以,例如

logextension.groovy

class logextension {
    string outputpath;
}

扩展在这里就是用来为插件配置 dsl 用的。

//为 项目添加了一个 logextension 类型的属性 名字是 log
project.extensions.create("log", logextension)

插件可以使用 dsl 接收参数,在插件或者任务里直接通过 project 实例访问即可。

def file = project.log.outputpath;

插件创建完成后,在项目的里就可以使用了。

现在可以使用类名应用插件了。

build.gradle

import com.github.skymxc.jarlogplugin

apply plugin: jarlogplugin

插件应用成功后就可以使用 dsl 为插件配置参数。

配置记录文件地址:

build.gradle

log {
    outputpath rootproject.projectdir.getpath()+"\\record\\jar.txt"
}

为插件创建 id

  1. 在 main 目录下创建 resources 文件夹
  2. 在 resources 目录下创建 meta-inf 文件夹
  3. 在 meta-inf 目录下创建 gradle-plugins 文件夹
  4. 在 gradle-plugins 目录下创建 properties 文件,名字就是你的插件 id。
  5. 在 id.properties 文件里通过 implementation-class 指向你的实现类。

例如

src / main / resources / meta-inf / gradle-plugins / com.github.skymxc.sample.properties

implementation-class= com.github.skymxc.jarlogplugin

然后就可以使用插件 id 了

plugins {
    id 'com.github.skymxc.sample'
}

关于插件 id 的规范:

  • 可以包含任何字母数字字符 “ . ”和 “ - ”。
  • 必须至少包含一个 “ . ” 。
  • 一般使用小写的反向域名。(类似包名)
  • 不能以 “ . ” 结尾。
  • 不能包含连续的 “ . ” 。

关于 groovy 的语法,可以参考 groovy 语法

在单独的项目里创建插件

这次仍然是使用 groovy 语言。

这里的插件项目其实就是一个 groovy 项目,当然了你如果使用 java 语言就是一个 java 工程。

创建一个工程

创建出来的项目就是这样子,标准的 groovy 工程目录

创建groovy工程

更改 build.gradle 脚本,配置项目

  1. 应用 maven-publih 插件
  2. 添加 gradle 和 groovy 的依赖
  3. 配置上传任务

最后就是这样子

plugins {
    id 'groovy'
    id 'maven-publish'
}

group 'com.github.skymxc'
version '1.0.0'

sourcecompatibility = 1.8

repositories {
    mavencentral()
}

//使用 groovy 和 gradle 依赖
dependencies {
    compile gradleapi()
    compile localgroovy()
}
publishing {
    repositories {
        maven {
            name 'local'
            url 'file://e:/libs/localmaven'
        }
    }
    publications {
        maven(mavenpublication) {
            groupid = 'com.github.skymxc'
            artifactid = 'plugin'
            version = '1.0.0'
            from components.java
        }
    }

}

创建两个插件:

一个是上面创建的那个,就不重复粘贴了。

另一个插件 greet,配置一个任务,简单的输出一句话。

class greet implements plugin<project> {
    @override
    void apply(project target) {
        target.extensions.create("hello", hello)
        target.task("hello") {
            dolast {
                println "message -> ${target.hello.message}"
            }
        }
    }
}

hello.groovy

class hello {
    string message
}

插件 id 的配置是跟上面一样的。

目录结构图

执行 maven-publish 的 publish 任务,将插件发布到指定仓库。

gradlew -p plugin publish

发布成功后的仓库

发布成功的图片

插件创建完成了,也发布了,下面就是使用这个插件了。

这里对插件的使用就简单介绍一下,详细的可以查看之前的这篇介绍:gradle 插件

  1. 在根项目的 build.gradle 配置仓库,添加依赖
buildscript {
    repositories {
        maven {
            url 'file://e:/libs/localmaven'
        }
    }
    dependencies {
        classpath 'com.github.skymxc:plugin:1.0.2'
    }
}
  1. 应用插件

我分别在两个 java 项目里使用了插件:

  • 一个是使用 id 的方式
  • 一个是使用类名的方式

lib_2/ build.gradle 使用 类名的方式

······

apply plugin:'com.github.skymxc.greet'

hello{
    message '使用了 com.github.skymxc.greet 插件'
}

······

lib_1/ build.gradle 使用 id 的方式

plugins {
    id 'java'
    id 'com.github.skymxc.jarlog'
}

······

logconfigure {
    outputpath rootproject.projectdir.getpath()+"\\record\\jar.txt"
}

应用插件后的 gradle 视图,可以看到已经添加的任务。

gradle 视图-任务

使用 java-gradle-plugin 开发插件

像上面一样创建一个项目,不过这次是一个 java 项目,然后应用这个插件。

java-gradle-plugin 可以减少重复代码,它自动的应用 java 插件,添加 gradleapi() 依赖。

plugins {
    id 'java-gradle-plugin'
}

使用 gradleplugin {} 配置块可以配置开发的每一个插件,不用手动创建对应的属性文件了。

gradleplugin {
    plugins {
        greetplugin {
            id = 'com.github.skymxc.greet'
            implementationclass = 'com.github.skymxc.greetplugin'
        }

        jarwithlogplugin {
            id = 'com.github.skymxc.jar-log'
            implementationclass = 'com.github.skymxc.jarwithlogplugin'
        }
    }
}

插件会在 jar 文件里自动生成对应的 meta-inf 目录。

配合 maven-publish 可以为每个插件创建对应的发布任务。

在发布时也会为每个插件发布对应的 “插件标记工件” 。

插件标记工件

关于 插件标记工件这里插一下:

每个 maven 工件都是由三部分标识的

  • groupid
  • artifactid
  • version

平常我们添加依赖的这样的:

implementation 'groupid:artifactid:version'

而我们的插件是通过 id 应用的,怎么通过 id 找到对应的工件呢,这就有了“插件标记工件”。
应用插件时会把 id 映射成这样:plugin.id: plugin.id.gradle.plugin:plugin.version

即:

  • plugin.id
  • plugin.id.gradle.plugin
  • plugin.version

举个上面的例子:com.github.skymxc.greet 插件对应的工件就是:

com.github.skymxc.greet:com.github.skymxc.greet.gradle.plugin:1.0.0

部分代码:

plugins {
    id 'java-gradle-plugin'
    id 'maven-publish'
}

group 'com.github.skymxc'
version '1.0.0'


gradleplugin {
    plugins {
        greetplugin {
            id = 'com.github.skymxc.greet'
            implementationclass = 'com.github.skymxc.greetplugin'
        }

        jarwithlogplugin {
            id = 'com.github.skymxc.jar-log'
            implementationclass = 'com.github.skymxc.jarwithlogplugin'
        }
    }
}

publishing {
    repositories {
        maven {
            name 'local'
            url 'file://e:/libs/localmaven'
        }
    }
}

maven-publish 的任务

简单介绍一下 maven-publish 的发布任务

  • generatepomfilefor${pubname}publication

    为名字为 pubname 的的发布创建一个 pom 文件,填充已知的元数据,例如项目名称,项目版本和依赖项。pom文件的默认位置是build / publications / $ pubname / pom-default.xml。

  • publish${pubname}publicationto${reponame}repository

    将 pubname 发布 发布到名为 reponame 的仓库。
    如果仓库定义没有明确的名称,则 reponame 默认为 “ maven”。

  • publish${pubname}publicationtomavenlocal

    将 pubname 发布以及本地发布的 pom 文件和其他元数据复制到本地maven缓存中
    (通常为$user_home / .m2 / repository)。

  • publish

    依赖于:所有的 publish${pubname}publicationto${reponame}repository 任务
    将所有定义的发布发布到所有定义的仓库的聚合任务。不包括复制到本地 maven 缓存的任务。

  • publishtomavenlocal

    依赖于:所有的 publish${pubname}publicationtomavenlocal 任务

    将所有定义的发布(包括它们的元数据(pom文件等))复制到本地maven缓存。

这张图列出了为每个插件生成的对应的任务。
插件对应的发布任务

执行发布任务 publish 后可以在对应的仓库查看

发布后的仓库图1
发布后的仓库图2

发布插件后的使用

  1. 配置仓库,这次在 settings.gradle 里配置
pluginmanagement {
    repositories {
        maven {
            url 'file://e:/libs/localmaven'
        }
    }
}
  1. 使用插件
plugins {
    id 'java'
    id 'com.github.skymxc.greet' version '1.0.13'
    id 'com.github.skymxc.jar-log' version '1.0.0'
}

应用插件后就可以在 gradle 的窗口看到对应的任务了。

然后可以根据需要配置 dsl 。

为插件配置 dsl

和插件的交互就是通过 dsl 配置块进行的。

那怎么为插件配置 dsl 呢,答案是随便一个普通类都可以的。

通过 gradle 的 api 可以将一个普通的类添加为 project 的扩展,即 project 的属性。

举个例子,插件里的任务需要两个参数:文件地址,文件名字,就要通过 dsl 配置的方式解决。

jarlogextension.java 一个普通的类,有两个属性,分别是 name , path

package com.github.skymxc.extension;

public class jarlogextension {
    private string name;
    private string path;

    //省略 setter/getter
}

在插件里将这个类添加为项目的扩展。

public class jarwithlogplugin implements plugin<project> {

    @override
    public void apply(project target) {
        //添加扩展
        target.getextensions().add("jarlog", jarlogextension.class);
        //创建任务
        target.gettasks().create("jarwithlog", jarwithlogtask.class);
    }
}

应用插件后就可以在脚本里使用这个 dsl 配置了。

build.gradle

······

/**
 * 为 jarwithlog 配置的 dsl
 */
jarlog {
    path getbuilddir().path+"/libs"
    name "record.txt"
}

······

接下来就是在插件或者任务里获取 dsl 配置的参数了。

可以通过名字或者类型获取到这个扩展对象。

public class jarwithlogtask extends jar {

    @taskaction
    private void writelog() throws ioexception {
      //获取到配置
        jarlogextension extension = getproject().getextensions().getbytype(jarlogextension.class);

        file file = new file(extension.getpath(),extension.getname());
        string s = file.getabsolutepath();
        string content = getnow()+" --- "+getarchivefilename().get();
        system.out.println("path --> "+s);
        writefile(s,content);
    }
}

嵌套 dsl

在我们日常的使用中,嵌套 dsl 很常见,那怎么实现的呢。

hello {
    message '使用 pluginmanagement 管理插件'
    user {
        name 'mxc'
        age 18
    }
}

现在我来实现下:

首先是创建里面的嵌套对象,需要注意的是要为 dsl 配置对应的方法。

userdata.java

package com.github.skymxc.extension;

/**
 * 为了实践嵌套 dsl 建的
 */
public class userdata {
    private string name;
    private int age;
    public string getname() {
        return name;
    }

    /**
     * 注意此方法 没有 set
     * @param name
     */
    public void name(string name) {
        this.name = name;
    }

    public int getage() {
        return age;
    }

    public void age(int age) {
        this.age = age;
    }

}

然后是外层的 dsl 对应的类,因为有 dsl 嵌套,所以要使用闭包

package com.github.skymxc.extension;

import org.gradle.api.action;

/**
 * 为 hellotask 创建的扩展,用于接收配置参数
 */
public class helloextension {

    private string message;
    private final userdata user = new userdata();


    /**
     * 注意此方法没有 set
     * @param action
     */
    public void user(action<? super userdata> action) {
        action.execute(user);
    }

    // 省略其他 getter/setter
}

最后就是添加到项目的扩展了,和前面一样

public class greetplugin implements plugin<project> {
    @override
    public void apply(project target) {
        target.getextensions().create("hello", helloextension.class);
        target.gettasks().create("hello", hellotask.class);
    }
}

在任务中的获取也是一样的

helloextension hello = getproject().getextensions().getbytype(helloextension.class);
userdata user = hello.getuser();

集合对象

再看一个 dsl 配置,这种集合嵌套也经常见到,下面也来简单实现一下。

fruits {
    apple {
        color '红色'
    }

    grape {
        color '紫红色'
    }

    banana {
        color '黄色'
    }

    orange {
        color '屎黄色'
    }

}

这种配置是配合 nameddomainobjectcontainer 实现的,它接收一个类,这个类必须有一个包含 name 参数的构造方法。

fruit.java

/**
 * 必须有一个 name 属性,并且有一个 name 参数的构造函数
 */
public class fruit {

    private string name;
    private string color;

    public fruit(string name) {
        this.name = name;
    }

    public void color(string color){
        setcolor(color);
    }

    //省略 setter/getter
}

配置一个 factory

fruitfactory.java

import org.gradle.api.nameddomainobjectfactory;
import org.gradle.internal.reflect.instantiator;

public class fruitfactory implements nameddomainobjectfactory<fruit> {

    private instantiator instantiator;

    public fruitfactory(instantiator instantiator) {
        this.instantiator = instantiator;
    }

    @override
    public fruit create(string name) {
        return instantiator.newinstance(fruit.class, name);
    }
}

接着就是创建 nameddomainobjectcontainer 对象并添加到 project 。

greetplugin.java

public class greetplugin implements plugin<project> {
    @override
    public void apply(project target) {

        instantiator instantiator = ((defaultgradle)target.getgradle()).getservices().get(instantiator.class);

        nameddomainobjectcontainer<fruit> fruits = target.container(fruit.class,new fruitfactory(instantiator));

        target.getextensions().add("fruits",fruits);

        target.gettasks().create("printlnfruits", showfruittask.class);
    }
}

现在应用这个插件就可以在脚本里使用上述的 dsl 配置了。

最后是 dsl 配置的接收了

public class showfruittask extends defaulttask {

    @taskaction
    public void show(){
        nameddomainobjectcontainer<fruit> fruits = (nameddomainobjectcontainer<fruit>) getproject().getextensions().getbyname("fruits");

        fruits.foreach(fruit -> {
            string format = string.format("name: %s , color: %s", fruit.getname(), fruit.getcolor());
            getlogger().quiet("fruit : {}",format);
        });
    }
}

关于自定义插件的相关介绍就这些了,更详细的文档可以查看 gradle 用户手册

这篇文章的源码已经放在 github 上:gradlepractice

资料

  • 自定义插件 https://docs.gradle.org/current/userguide/custom_plugins.html
  • 开发辅助插件 https://docs.gradle.org/current/userguide/java_gradle_plugin.html
  • 使用插件 https://docs.gradle.org/current/userguide/plugins.html
  • 发布 https://docs.gradle.org/current/userguide/publishing_overview.html
  • maven 发布插件 https://docs.gradle.org/current/userguide/publishing_maven.html
  • gradle 教程 https://gradle.org/guides/?q=plugin%20development
  • gradle dsl https://blog.csdn.net/zlcjssds/article/details/79229209

end

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网