将 Spotless 包含进自己的 Plugin
将 Spotless 包含进自己的 Plugin
问题描述
在 Android / JVM 新项目建立之初我喜欢起手应用 spotless Gradle 插件, 这个插件包含了如代码格式化检查之类在内的许多方便的功能。问题在于 spotless 还需要在 build.gradle.kts
构建文件中的 spotless { ... }
闭包配置才能真正工作,但我太懒不想每次都写。所以最终决定自己实现一个插件,引入 Spotless 的同时,提供一份我认为合适的初始配置。
调查
Gradle 提供了几种插件实现的方式,我希望实现一个二进制插件,precompiled 插件感觉需要到处复制会比较麻烦。
Gradle 插件的编程表示是一个实现了 Plugin 接口的类。具体需要实现其中的 apply(project: Project)
方法,传参为当前作用的类。
为了让 Kotlin 编程更舒适,引入 kotlin-dsl
插件,它提供了一些安全的 Java 到 Kotlin 的封装,同时因为它内部带了 kotlin 插件所以新建项目的时候 kotlin("jvm")
这样的插件声明可以抹掉, gradleApi()
也可以不用了。
实现
目前我希望做几个功能:
- 在应用之前,检查是否已经有 spotless 插件了,有则警告;
- 应用 Spotless 和自定义配置
- 提供修改自定义配置的方法()
- 发布到个人 Maven 仓库()
要 implementation()
不要 id()
在之前没研究过怎么实现插件前我一直觉得奇怪,我我发现 很多插件在 Gradle Plugin Portal 和 MavenCentral 都有! 当时一直无法理解,我在 Gradle 构建文件里需要用插件,是在 plugin { id(...) }
里面声明就行了,在 Gradle Plugin Portal 中存一份就可以了,Maven 中的是干嘛的?
而在我准备做一个二进制插件的时候我终于明白了这是干什么的了,Gradle 文档指出 plugin { ... }
包括进来的插件只能用于拓展 Gradle 脚本的功能,换言之它没法直接拓展项目里 Java / Kotlin 代码能用的功能(但是它可以用于 precompiled 插件,因为 precompiled 插件本身就是 Gradle 脚本);要将插件作为完整的项目,拓展项目代码的功能自然是要走 implementation(...)
声明来引入库了。现在要实现二进制插件,我必须将 Spotless Gradle 插件通过 implementation()
的方式引进来,这样才可以在 Kotlin 代码中操作其中的类执行插件相关操作。
现在需要的声明如下:
implementation("com.diffplug.spotless:spotless-plugin-gradle:6.25.0")
首先创建一个类,继承 Plugin
接口,实现其中的 apply()
方法。
我说 SpotlessPlugin()
,来了吗?
Plugin 的创建不算复杂,构造函数没有任何参数,在代码里面直接使用构造函数没有问题:
val spotlessPlugin = SpotlessPlugin()
创建多个同一个 Plugin 实例也没问题。不过多个插件实例不能对同一个项目 apply()
,否则会有名字冲突。
插件配置项是应用了插件之后才能获取的,使用反射:
val spotlessExt = extensions.getByType(SpotlessExtension::class)
spotlessExt.kotlin {
ktlint().setEditorConfigPath(internalEditorConfigPath)
target("**/*.kt")
trimTrailingWhitespace()
endWithNewline()
}
剩下的配置方面内容就不再叙述了。到此为止 Spotless Plugin 已经被这个插件拉取并且做了一些配置了。
插件,它是什么
编译完毕后,插件会在构建目录下形成 jar 包。
JAR stands for Java ARchive. It's a file format based on the popular ZIP file format and is used for aggregating many files into one. Although JAR can be used as a general archiving tool, the primary motivation for its development was so that Java applets and their requisite components (.class files, images and sounds) can be downloaded to a browser in a single HTTP transaction, rather than opening a new connection for each piece.
Gradle 插件与一般 Java 库并无本质不同,也是形成一份 jar,随后让 Gradle 这个运行在 JVM 之上的程序使用。
踩过的坑
- 不要在某个 task 的配置里操作其他 task(例如 plugin apply 就会注册一些 tasks),否则将出现上下文错误(gradle#27030)
- 把相关代码写在
apply()
重载方法里,而不是如Task.register()
等方法携带的任务配置块里。 - 如果需要依赖其他 task 执行,使用
dependesOn()
。
- 把相关代码写在
后续
插件实现是类,并不神秘。它运行在 Gradle 提供的上下文中,能够从 Gradle 那儿得到一些信息和可用的操作。
最后,东西是基本做出来了,下一步我希望加入可配置的 extensions
,目前 editorconfig
配置怎么整合到插件中还在考虑(主要是 editorconfig 的路径问题,这部分可能要看 jar 包怎么使用非代码数据,非代码数据放在哪里的介绍);
同时,我希望插件存在于某一个远程仓库中,这样用起来更方便。因为我不想将基本是自用的玩具工程传到 Gradle Plugin Potral 之类的公共仓库中去,所以需要自己弄一个仓库以及仓库发布相关的控制代码,因此这个插件距离能真正舒适地使用还有很长一段距离。