经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 移动开发 » Android » 查看文章
Gradle 自定义插件
来源:cnblogs  作者:佛系编码  时间:2019/10/25 8:32:00  对本文有异议

思维导图

使用版本 5.6.2

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

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

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

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

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

可以在三个地方定义插件

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

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

在脚本里的插件

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

在 buildSrc 下

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

在单独的项目里

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

创建一个插件

建议使用静态语言,例如 Java ,Kotlin,开发工具建议使用 IntelliJ IDEA 。

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

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

CustomPLugin.java

  1. // 定义一个插件
  2. class CustomPLugin implements Plugin<Project>{
  3. @Override
  4. void apply(Project target) {
  5. // do something
  6. }
  7. }

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

在脚本里创建一个插件

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

build.gradle

  1. // 定义一个插件
  2. class CustomPLugin implements Plugin<Project>{
  3. @Override
  4. void apply(Project target) {
  5. //添加一个任务
  6. target.task('hello', group: 'util') {
  7. doLast {
  8. logger.quiet("Hello Plugin.")
  9. }
  10. }
  11. }
  12. }
  13. //直接在脚本里应用
  14. apply plugin:CustomPLugin

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

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

  1. 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

  1. /**
  2. * 输出 生成记录到指定文件
  3. */
  4. class JarLogPlugin implements Plugin<Project> {
  5. @Override
  6. void apply(Project target) {
  7. //增加一个扩展配置用来接收参数
  8. target.extensions.create("log", LogExtension)
  9. //添加一个任务
  10. target.task(type: Jar,group:'util','jarWithLog',{
  11. doLast {
  12. //使用配置
  13. def file = target.log.outputPath;
  14. if (file==null){
  15. file = new File(target.projectDir,"/log/jarlog.txt").getPath()
  16. }
  17. println "存储目录是 ${file}"
  18. def content = "${getArchiveFileName().get()}---${getNow()}\n"
  19. writeFile(file,content)
  20. }
  21. })
  22. //为 jar 任务添加一个 操作,
  23. target.tasks.jar.doLast {
  24. println "当前时间是 ${getNow()},打了一个 jar-> ${version}"
  25. //存到指定文件记录
  26. def file = new File(target.projectDir,"/log/jarlog.txt");
  27. def content = "${version}---${getNow()}\n"
  28. writeFile(file.getAbsolutePath(),content)
  29. }
  30. }
  31. def String getNow(){
  32. def dateFormat = new SimpleDateFormat("YYYY-MM-dd HH:mm:ss.SSS");
  33. return dateFormat.format(Calendar.getInstance().getTime());
  34. }
  35. def void writeFile(String path,String content){
  36. def file = new File(path);
  37. if (!file.exists()){
  38. if (!file.getParentFile().exists()){
  39. file.getParentFile().mkdirs();
  40. }
  41. file.createNewFile();
  42. }
  43. FileWriter writer = new FileWriter(file.getAbsolutePath(),true);
  44. BufferedWriter bufferedWriter = new BufferedWriter(writer);
  45. bufferedWriter.write(content);
  46. bufferedWriter.close();
  47. }
  48. }

配置 DSL

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

LogExtension.groovy

  1. class LogExtension {
  2. String outputPath;
  3. }

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

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

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

  1. def file = project.log.outputPath;

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

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

build.gradle

  1. import com.github.skymxc.JarLogPlugin
  2. apply plugin: JarLogPlugin

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

配置记录文件地址:

build.gradle

  1. log {
  2. outputPath rootProject.projectDir.getPath()+"\\record\\jar.txt"
  3. }

为插件创建 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

  1. implementation-class= com.github.skymxc.JarLogPlugin

然后就可以使用插件 ID 了

  1. plugins {
  2. id 'com.github.skymxc.sample'
  3. }

关于插件 id 的规范:

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

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

在单独的项目里创建插件

这次仍然是使用 Groovy 语言。

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

创建一个工程

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

创建Groovy工程

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

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

最后就是这样子

  1. plugins {
  2. id 'groovy'
  3. id 'maven-publish'
  4. }
  5. group 'com.github.skymxc'
  6. version '1.0.0'
  7. sourceCompatibility = 1.8
  8. repositories {
  9. mavenCentral()
  10. }
  11. //使用 groovy 和 gradle 依赖
  12. dependencies {
  13. compile gradleApi()
  14. compile localGroovy()
  15. }
  16. publishing {
  17. repositories {
  18. maven {
  19. name 'local'
  20. url 'file://E:/libs/localMaven'
  21. }
  22. }
  23. publications {
  24. maven(MavenPublication) {
  25. groupId = 'com.github.skymxc'
  26. artifactId = 'plugin'
  27. version = '1.0.0'
  28. from components.java
  29. }
  30. }
  31. }

创建两个插件:

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

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

  1. class Greet implements Plugin<Project> {
  2. @Override
  3. void apply(Project target) {
  4. target.extensions.create("hello", Hello)
  5. target.task("hello") {
  6. doLast {
  7. println "message -> ${target.hello.message}"
  8. }
  9. }
  10. }
  11. }

Hello.groovy

  1. class Hello {
  2. String message
  3. }

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

目录结构图

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

gradlew -p plugin publish

发布成功后的仓库

发布成功的图片

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

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

  1. 在根项目的 build.gradle 配置仓库,添加依赖
  1. buildscript {
  2. repositories {
  3. maven {
  4. url 'file://E:/libs/localMaven'
  5. }
  6. }
  7. dependencies {
  8. classpath 'com.github.skymxc:plugin:1.0.2'
  9. }
  10. }
  1. 应用插件

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

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

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

  1. ······
  2. apply plugin:'com.github.skymxc.greet'
  3. hello{
  4. message '使用了 com.github.skymxc.greet 插件'
  5. }
  6. ······

lib_1/ build.gradle 使用 id 的方式

  1. plugins {
  2. id 'java'
  3. id 'com.github.skymxc.jarlog'
  4. }
  5. ······
  6. logConfigure {
  7. outputPath rootProject.projectDir.getPath()+"\\record\\jar.txt"
  8. }

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

gradle 视图-任务

使用 java-gradle-plugin 开发插件

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

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

  1. plugins {
  2. id 'java-gradle-plugin'
  3. }

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

  1. gradlePlugin {
  2. plugins {
  3. greetPlugin {
  4. id = 'com.github.skymxc.greet'
  5. implementationClass = 'com.github.skymxc.GreetPlugin'
  6. }
  7. jarWithLogPlugin {
  8. id = 'com.github.skymxc.jar-log'
  9. implementationClass = 'com.github.skymxc.JarWithLogPlugin'
  10. }
  11. }
  12. }

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

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

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

插件标记工件

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

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

  • groupId
  • artifactId
  • version

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

  1. 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

部分代码:

  1. plugins {
  2. id 'java-gradle-plugin'
  3. id 'maven-publish'
  4. }
  5. group 'com.github.skymxc'
  6. version '1.0.0'
  7. gradlePlugin {
  8. plugins {
  9. greetPlugin {
  10. id = 'com.github.skymxc.greet'
  11. implementationClass = 'com.github.skymxc.GreetPlugin'
  12. }
  13. jarWithLogPlugin {
  14. id = 'com.github.skymxc.jar-log'
  15. implementationClass = 'com.github.skymxc.JarWithLogPlugin'
  16. }
  17. }
  18. }
  19. publishing {
  20. repositories {
  21. maven {
  22. name 'local'
  23. url 'file://E:/libs/localMaven'
  24. }
  25. }
  26. }

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 里配置
  1. pluginManagement {
  2. repositories {
  3. maven {
  4. url 'file://E:/libs/localMaven'
  5. }
  6. }
  7. }
  1. 使用插件
  1. plugins {
  2. id 'java'
  3. id 'com.github.skymxc.greet' version '1.0.13'
  4. id 'com.github.skymxc.jar-log' version '1.0.0'
  5. }

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

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

为插件配置 DSL

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

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

通过 Gradle 的 API 可以将一个普通的类添加为 Project 的扩展,即 Project 的属性。

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

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

  1. package com.github.skymxc.extension;
  2. public class JarLogExtension {
  3. private String name;
  4. private String path;
  5. //省略 setter/getter
  6. }

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

  1. public class JarWithLogPlugin implements Plugin<Project> {
  2. @Override
  3. public void apply(Project target) {
  4. //添加扩展
  5. target.getExtensions().add("jarLog", JarLogExtension.class);
  6. //创建任务
  7. target.getTasks().create("jarWithLog", JarWithLogTask.class);
  8. }
  9. }

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

build.gradle

  1. ······
  2. /**
  3. * 为 jarWithLog 配置的 DSL
  4. */
  5. jarLog {
  6. path getBuildDir().path+"/libs"
  7. name "record.txt"
  8. }
  9. ······

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

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

  1. public class JarWithLogTask extends Jar {
  2. @TaskAction
  3. private void writeLog() throws IOException {
  4. //获取到配置
  5. JarLogExtension extension = getProject().getExtensions().getByType(JarLogExtension.class);
  6. File file = new File(extension.getPath(),extension.getName());
  7. String s = file.getAbsolutePath();
  8. String content = getNow()+" --- "+getArchiveFileName().get();
  9. System.out.println("path --> "+s);
  10. writeFile(s,content);
  11. }
  12. }

嵌套 DSL

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

  1. hello {
  2. message '使用 pluginManagement 管理插件'
  3. user {
  4. name 'mxc'
  5. age 18
  6. }
  7. }

现在我来实现下:

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

UserData.java

  1. package com.github.skymxc.extension;
  2. /**
  3. * 为了实践嵌套 DSL 建的
  4. */
  5. public class UserData {
  6. private String name;
  7. private int age;
  8. public String getName() {
  9. return name;
  10. }
  11. /**
  12. * 注意此方法 没有 set
  13. * @param name
  14. */
  15. public void name(String name) {
  16. this.name = name;
  17. }
  18. public int getAge() {
  19. return age;
  20. }
  21. public void age(int age) {
  22. this.age = age;
  23. }
  24. }

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

  1. package com.github.skymxc.extension;
  2. import org.gradle.api.Action;
  3. /**
  4. * 为 HelloTask 创建的扩展,用于接收配置参数
  5. */
  6. public class HelloExtension {
  7. private String message;
  8. private final UserData user = new UserData();
  9. /**
  10. * 注意此方法没有 set
  11. * @param action
  12. */
  13. public void user(Action<? super UserData> action) {
  14. action.execute(user);
  15. }
  16. // 省略其他 getter/setter
  17. }

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

  1. public class GreetPlugin implements Plugin<Project> {
  2. @Override
  3. public void apply(Project target) {
  4. target.getExtensions().create("hello", HelloExtension.class);
  5. target.getTasks().create("hello", HelloTask.class);
  6. }
  7. }

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

  1. HelloExtension hello = getProject().getExtensions().getByType(HelloExtension.class);
  2. UserData user = hello.getUser();

集合对象

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

  1. fruits {
  2. apple {
  3. color '红色'
  4. }
  5. grape {
  6. color '紫红色'
  7. }
  8. banana {
  9. color '黄色'
  10. }
  11. orange {
  12. color '屎黄色'
  13. }
  14. }

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

Fruit.java

  1. /**
  2. * 必须有一个 name 属性,并且有一个 name 参数的构造函数
  3. */
  4. public class Fruit {
  5. private String name;
  6. private String color;
  7. public Fruit(String name) {
  8. this.name = name;
  9. }
  10. public void color(String color){
  11. setColor(color);
  12. }
  13. //省略 setter/getter
  14. }

配置一个 Factory

FruitFactory.java

  1. import org.gradle.api.NamedDomainObjectFactory;
  2. import org.gradle.internal.reflect.Instantiator;
  3. public class FruitFactory implements NamedDomainObjectFactory<Fruit> {
  4. private Instantiator instantiator;
  5. public FruitFactory(Instantiator instantiator) {
  6. this.instantiator = instantiator;
  7. }
  8. @Override
  9. public Fruit create(String name) {
  10. return instantiator.newInstance(Fruit.class, name);
  11. }
  12. }

接着就是创建 NamedDomainObjectContainer 对象并添加到 Project 。

GreetPlugin.java

  1. public class GreetPlugin implements Plugin<Project> {
  2. @Override
  3. public void apply(Project target) {
  4. Instantiator instantiator = ((DefaultGradle)target.getGradle()).getServices().get(Instantiator.class);
  5. NamedDomainObjectContainer<Fruit> fruits = target.container(Fruit.class,new FruitFactory(instantiator));
  6. target.getExtensions().add("fruits",fruits);
  7. target.getTasks().create("printlnFruits", ShowFruitTask.class);
  8. }
  9. }

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

最后是 DSL 配置的接收了

  1. public class ShowFruitTask extends DefaultTask {
  2. @TaskAction
  3. public void show(){
  4. NamedDomainObjectContainer<Fruit> fruits = (NamedDomainObjectContainer<Fruit>) getProject().getExtensions().getByName("fruits");
  5. fruits.forEach(fruit -> {
  6. String format = String.format("name: %s , color: %s", fruit.getName(), fruit.getColor());
  7. getLogger().quiet("fruit : {}",format);
  8. });
  9. }
  10. }

关于自定义插件的相关介绍就这些了,更详细的文档可以查看 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

原文链接:http://www.cnblogs.com/skymxc/p/gradle-custom-plugin.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号