以自定义一个TestPlugin 的插件为例,记录一下遇到的坑。
一. 自定义Gradle 插件的步骤
1. 创建一个插件的项目
本例是在一个project 里创建一个module,module 名就叫做testplugin。创建完之后,目录结构可能跟plugin 所需的有些不一致,可以先将无用的目录删除掉,然后在main 文件夹下创建groovy 文件用来保存groovy 代码,
同时在main 目录下创建resources 目录,在resource 目录下创建META-INF 目录,在META-INF 目录下创建gradle-plugins 目录,啰嗦了这么多是因为这块有坑。最后形成的文件结构如下图所示:
:注意箭头所指处,
META-INF/gradle-plugins
被简写成了这种样式,这个我们也都明白,但是注意在新建目录时不要直接写成这种 "点" 的形式,因为 "点" 也会被作为目录名而不是下级目录,但是在新建包时是可以直接写点的。
在gradle-plugins 目录下创建xxx.properties 文件,这个文件的意义就是为我们自定义的plugin 声明了id 为xxx。当我们在使用plugin 时会有这样一句:
apply plugin: 'xxx'
上面这句的 xxx 就是plugin 的 id。另外xxx.properties 的内容如下:
implementation-class=com.whx.testplugin.TestPlugin
后面的class 根据自己的实现来改。
2. 配置gradle 文件
上图中的build.gradle 文件,为了能开发插件,需要进行相关配置:
// 应用groovy,plugin 开发是基于groovy 语言的
apply plugin: 'groovy'
// 应用maven,用于将插件上传至maven 仓库
apply plugin: 'maven'
buildscript {
repositories {
jcenter()
mavenCentral()
}
}
repositories {
jcenter()
mavenCentral()
}
// 定义组,发布plugin 使用
group='com.whx.testPlugin'
// plugin 版本
version='1.0.0'
// 一个将plugin 上传到maven 仓库的Task
uploadArchives {
repositories {
mavenDeployer {
// 定义插件打包后上传的位置,可以随意指定,但是在使用时需要指定同样的文件才能找到
// 这里指定的是本地相对路径
repository(url: uri('../repo'))
}
}
}
dependencies {
compile gradleApi() // groovy API
compile localGroovy() // groovy API
compile 'com.android.tools.build:gradle:3.0.0' // 如果Android 编译相关,需依赖这个
}
配置了gradle 文件之后,就可以进行plugin 的开发了,在定义了uploadArchives 之后,会在testplugin 这个module 下产生一个名为uploadArchives 的Task,双击即可运行
这里指定的上传地址是本地地址,uploadArchives 运行成功之后就会在本地自定义的路径下看到一些文件,如图
1.0.0 是版本号,上级路径是包名,这里省略了,生成了这些文件,说明我们的插件生成成功了。
3. 编写代码
要实现plugin 的功能,需要创建一个类,实现Plugin<T> 接口,及apply 方法:
class TestPlugin implements Plugin<Project> {
@Override
void apply(Project project) {
// 创建一个extension
project.extensions.create("testPlugin", TestPluginExtension)
// 创建一个task
project.task('hello') {
doLast {
println("Hello ${project.testPlugin.filePath}")
}
}
}
}
上面提到了创建一个extension,这个类的作用就是为插件提供一些可配置的属性,也就是我们可以从使用插件的模块的gradle 文件配置属性值来供插件使用。
class TestPluginExtension {
String filePath
}
: 在新建groovy 文件时,如果采用 new → File → 文件名 → Register New File Type Association(选groovy)的方式创建groovy 文件有时候是不会为文件加 ".groovy" 后缀名的,如果没有后缀名,那么编译时就不会将这个文件编译,编译器不会报错,但是生成plugin 之后,在使用plugin 时就会报找不到某个类的错误。所以在新建一个文件之后要注意检查是不是有正确的后缀名,不要过于相信IDE。
上面的plugin 类中,除了创建一个extension 来定义可配置属性外,还创建了一个Task,如注释所写。上面这种方式是在plugin 类里直接定义了一个Task 的简单方式,当然也可以把Task 抽出来写成一个独立的类
class MagicTask extends DefaultTask{
String filePath
@TaskAction
def taskMethod() { // task 要执行的任务代码
filePath = project.testPlugin.filePath // 1
new SolveFile().solve(filePath) // 2
}
}
上面的filePath 的值可以直接从project 对象获取,如1处所示,由于groovy 也是基于JVM 的,也像Kotlin 一样能够无缝调用Java 代码,2处代码实际就是调用的Java 类的方法。
4. 使用自定义plugin
使用plugin 也比较简单,首先在Project 级的build.gradle 配置一下依赖
build.gradle(Project 级)
buildscript {
...
repositories {
...
maven {
url uri('./repo')
}
}
dependencies {
...
classpath 'com.whx.testPlugin:testplugin:1.0.0'
}
}
由于我们自定义plugin 时是上传的maven 仓库,所以在使用时的依赖库也要用maven,并用url 来指定地址。
:由于是本地的相对路径,注意路径的正确性,另外dependencies 中要使用classpath 声明,后面的形式是 “包名:module 名:版本号”。注意这些配置如果放在模块级 build.gradle 文件的话,maven 的url 地址可能会覆盖project 中的配置,从而导致依赖找不到等奇怪错误。
接下来,在我们要使用的模块的 build.gradle 文件中应用plugin 并配置属性:
build.gradle(module 级)
apply plugin: 'testPlugin' // 1
testPlugin { // 2
filePath = projectDir.absolutePath + '/src'
}
...
第一行就是应用我们的插件,第二行就是配置一些属性值来供插件使用,这些属性就是我们在TestPluginExtension 中定义的。
:第一行,应用的plugin ,值是我们自定义plugin 的id,这个值就是上面说的xxx.properties 的xxx,如果没有创建这个properties 文件,那么这个id 默认是实现了Plugin<T> 接口的类的全限定名。
至此,一个简单的自定义插件就完成了,只要避过了上面的几个坑就基本能跑通了。build 一下项目之后,会生成我们在plugin 类中声明的task,在Project 下,进入命令行输入
./gradlew taskName
就会执行Task。
另外关于自定义plugin,还有一些其他内容,包括groovy 的语法特性,gradle 的运行过程,plugin 的调试等,由于篇幅限制就不一一介绍了,这里就简单介绍一下gradle 的构建过程:
gradle构建的过程总共分为三个阶段:初始化阶段、配置阶段、运行阶段。
初始化阶段是执行settings.gradle文件中的内容,看看这个Project需要构建哪几个module。
配置阶段是从根Project依次遍历module,并为每个module生成一个Project对象,配置阶段完成时就形成了一个完整的task依赖图,同时配置阶段还会执行Task/Plugin 的初始化方法,定义期语句,configure 语句等,但不会执行真正要执行的内容。
执行阶段就是执行Task,执行阶段不依赖脚本的定义顺序。
那么apply方法是什么时候执行的呢?
是在配置阶段遇到apply plugin:'testPlugin' 就开始执行, apply方法中传入的Project对象就是某个使用该插件的Project的对象。
二. 自定义gradle 插件的建议
可以看到plugin 就是一个 Plugin + Extension + Task 的一个模式,接收用户配置执行特定Task,所以我们可以把Plugin 类作为一个载体,只负责创建Extension 和Task,而不要实现业务逻辑;同时如果Task 比较多的话,可以创建一个辅助类TaskManager 来管理Task,另外Task 也是groovy 文件,如果我们对groovy 不熟悉,可以在Task 中调用Java 代码来实现功能。