玩转Gradle构建工具(六)、插件编写

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第6天,点击查看活动详情

前言

本系列目录

  1. Task
  2. Project、Task常用API
  3. 文件操作
  4. 依赖管理
  5. 多模块构建
  6. 插件编写
  7. SpringBoot插件源码分析
  8. 过度到Kotlin

平常我们都会多多少少用到Gradle插件,比如java插件,他提供了如打包jar的功能,所以这章我们来学习下如何编写一个插件,来提高工作中繁琐的步骤。

Gradle有三个地方可以存放插件代码。

  • build.gradle

    这种方式就是直接把插件的代码放在build.gradle里面,开发插件也不会采用这种方式,就不说了。

  • buildSrc

    这种方式将插件的代码放在rootProjectDir/buildSrc/src/main/{java|kotlin|groovy}中,注意看,Gradle插件可以使用任何在JVM上运行的语言,最常见的就是java、kotlin、groovy,这种方式针的场景是插件不会对外使用,只供本项目使用。

  • 独立项目

    这种方式是最常见的,最终将插件打包成jar包,可以分享给其他项目使用,或者发布到maven公共仓库,而我们学习的就是这种方式。

创建插件项目

gradle本身就提供了一个插件项目模板,可以使用下面方式创建。

gradle init
复制代码

之后会让你选择项目类型,则选4,其余看自己的喜好,比如用java还是kotlin编写。

Select type of project to generate:
  1: basic
  2: application
  3: library
  4: Gradle plugin
Enter selection (default: basic) [1..4] 
复制代码

之后就可以使用IDEA打开了,这个模板默认创建了一个task,只输出了一句话,具体API我们在后面说。

image.png

首先看一下build.gradle中这段配置,我们需要给插件起一个id,比如这里是test-gradle-plugin,还有要指定这个插件的实现类。

注意,plugins下的greeting是可以随便起的。

gradlePlugin {
    // Define the plugin
    plugins {
        greeting {
            id = 'test-gradle-plugin'
            implementationClass = 'com.hxl.plugin.GradlePluginPlugin'
        }
    }
}
复制代码

配置完这些信息就可以打包了,直接执行jar任务即可,打包出来就是一个插件包,位于目录build/libs下。

引入

打包成jar后接下来看下如何在其他项目中引入,需要在settings.gradle中配置,flatDir告诉插件所在的目录,classpath告诉插件的名字,上面我们打包出来的插件默认名就是plugin.jar,但必须在前面加:

# settings.gradle
pluginManagement {
    resolutionStrategy {
        eachPlugin {
           println(requested.id)
        }
    }
    buildscript {
        repositories {
            flatDir  dirs: "/home/HouXinLin/project/gradle-plugin/plugin/build/libs/"
        }
        dependencies {
            classpath(":plugin")
        }
    }
}
复制代码

接下来需要在build.gradle中引入这个插件,如下,这里的id就是在插件项目中配置的,如果你细心的话,可以查看打包出来的jar包,有这样一个路径/META-INF/gradle-plugins/test-gradle-plugin.properties,这也是gradle查找插件的一个约定,改插件名也可以从这里把这个文件名改了。

plugins {
    id 'java'
    id("test-gradle-plugin")
}
复制代码

由于我们在插件项目中创建了一个任务greeting,所以在这里可以直接执行。

$ ./gradlew greeting
Hello from plugin 'gradle.plugin.greeting'
复制代码

java-gradle-plugin 插件

他是用来开发gradle插件的一个插件,上面方式创建的插件项目默认就会引入这个插件。

插件入口Plugin

编写插件时需要实现一个Plugin接口,Gradle会实例化他并调用他的Plugin.apply()方法,参数是一个Project对象,Project对象我们在前面已经说过了,是Gradle API开始的地方,可以通过他创建任务,获取项目构建路径等,比如下面,是创建一个greeting的任务,并输出一句话。

public class GradlePluginPlugin implements Plugin<Project> {
    public void apply(Project project) {
        project.getTasks().register("greeting", task -> {
            task.doLast(s -> System.out.println("Hello from plugin 'gradle.plugin.greeting'"));
        });
    }
}
复制代码

扩展

插件可能还需要一些配置选项,用来为插件提供一些配置,Project有一个关联的ExtensionContainer对象来做到这一点。

由于上面示例中输出的字符是死的,所以下面我们通过这个特性把他改成活得。

如下,我们创建一个名为testExtension的扩展,类型是TestPluginExtension。

class TestPluginExtension {
     String message = "default";
}
public class GradlePluginPlugin implements Plugin<Project> {
    public void apply(Project project) {
        TestPluginExtension testPluginExtension=  project.getExtensions().create("testExtension", TestPluginExtension.class);
        project.getTasks().register("greeting", task -> {
            task.doLast(s -> System.out.println(testPluginExtension.message));
        });
    }
}
复制代码

然后在项目中就可以使用testExtension来获取或设置当前message的值。

plugins {
    id("test-gradle-plugin")
}
testExtension.message="message"
复制代码

当我们执行greeting任务时,如果没有配置message,那么默认就打印default。

还可以这样配置。

testExtension{
    message="message"
}
复制代码

当然还可以嵌套,其中ObjectFactory是用于创建各种model的工厂,他的实例可以通过构造方法或方法上标有javax.inject.Inject注解注入获得,它也可以通过Project.getObjects()获取。

ObjectFactory这种方式是在参考其他文章所知道的,但不知这样做的意义在哪里,当然也可以不使用他,直接new。

class TestPluginExtension {
    String message = "default";
    User user;

    @Inject
    public TestPluginExtension(ObjectFactory objectFactory) {
        this.user = objectFactory.newInstance(User.class);
    }

    void user(Action<? super User> action) {
        action.execute(user);
    }
}

class User {
    String name = "name";

    public User() {

    }
}

public class GradlePluginPlugin implements Plugin<Project> {
    public void apply(Project project) {
        TestPluginExtension testPluginExtension = project.getExtensions().create("testExtension", TestPluginExtension.class);


        project.getTasks().register("greeting", task -> {
            task.doLast(s -> System.out.println(testPluginExtension.user.name + "  " + testPluginExtension.message));
        });

    }
}
复制代码
plugins {
    id("test-gradle-plugin")
}
testExtension{
    message ='message'
    user{
        name="111"
    }
}
复制代码

这样在执行任务时,输出如下。

> Task :greeting
111  message
复制代码

独立task类

如果task的逻辑比较多,可以单独使用一个类。如下, 标有@TaskAction注解的方法告诉Gradle这是任务执行的方法。

class MyTask extends DefaultTask {
    private Project project;
    @Inject
    public MyTask(Project project) {
        this.project = project;
    }
    @TaskAction
    public void action() {
        System.out.println(this.project.getRootDir());
    }
}
public class GradlePluginPlugin implements Plugin<Project> {
    public void apply(Project project) {
        project.getTasks().register("greeting", MyTask.class,project);
    }
}
复制代码

设置task组

默认创建的task,都会在other中,如果想指定一个组,可以通过setGroup设置组名。

public class GradlePluginPlugin implements Plugin<Project> {
    public void apply(Project project) {
        TestPluginExtension testPluginExtension = project.getExtensions().create("testExtension", TestPluginExtension.class);
        TaskProvider<Task> greeting = project.getTasks().register("greeting", task -> {
            task.doLast(s -> System.out.println(testPluginExtension.user.name + "  " + testPluginExtension.message));
        });
        greeting.get().setGroup("testGroup");
    }
}
复制代码

同样如果在脚本中,也可以指定一个组。

task taskA{
    group("testgroup")
    doLast {
        println("a")
    }
}
复制代码

image.png

追加现有任务

如上,我们的greeting任务是在jar包中的,如果我们想在greeting执行结束后,在执行一个我们自己的任务,可以这样做。

tasks.greeting{
    doLast {
        println("test")
    }
}
tasks.getByName("greeting"){
    doLast {
        println("getByName")
    }
}
tasks.named("greeting"){
    it.doLast {
        println("named")
    }
}
复制代码

tasks是TaskContainer的实例,他责管理所有task,我们可以使用他提供的某种方法来定位一个现有任务,例如TaskCollection.getByName(String)

doLast、doFirst会追加到自身任务队列的最后、最前,并依次执行,而像group等会进行覆盖。

猜你喜欢

转载自juejin.im/post/7105221596624715789
今日推荐