探讨Gradle插件的自定义有助更好的理解Gradle,本文综合了userguide中的几篇相关文档,将不单独给出链接(除非很有必要),主要有:
Gradle插件中创建可重用构建逻辑的过程,然后可以将其应用于其他Gradle构建。Gradle的核心提供了构建任何东西的基础架构,但插件允许构建脚本作者以最少的努力完成工作,使他们能够专注于构建的内容,而不是如何构建。插件可以应用约定,添加新任务类型,与第三方工具和库集成等。例如,java插件提供标准的源目录布局和任务,例如compileJava和compileTestJava。
Gradle插件打包可重用的构建逻辑片段,可用于许多不同的项目和构建。Gradle允许自定义插件,可以重用构建逻辑,并与其他人分享。可以使用任何语言实现Gradle插件,前提是实现最终能编译为字节码。示例中,我们将使用Groovy作为实现语言。Groovy,Java或Kotlin都是用于实现插件的语言的不错选择,因为Gradle API已经设计为可以很好地使用这些语言。通常,使用Java或Kotlin实现的静态类型的插件将比使用Groovy实现的相同插件执行得更好。
打包插件
可以放置Gradle插件代码的几个地方:
构建脚本
直接在build.gradle中包含代码, 自动编译. 对外不可见,不可重用.buildSrc
工程
插件代码放在rootProjectDir/buildSrc/src/main/groovy
目录. Gradle 编译、测试插件,并添加到在build.gradle的classpath中. 对每个构建脚本可见. 对外不可见,不可重用.独立工程
独立的插件程序, 生成发布一个可供多个构建共享的JAR. 一般 JAR可以包含一些插件或任务.
简单插件
要创建Gradle插件,需要编写一个实现Plugin
接口的类。当插件应用于项目时,Gradle会创建插件类的实例并调用实例的Plugin.apply()
方法。项目对象作为参数传递,插件可以使用它来自由配置项目。以下示例包含greeting插件,该插件向项目添加hello任务。
build.gradle
class GreetingPlugin implements Plugin<Project> {
void apply(Project project) {
project.task('hello') {
doLast {
println 'Hello from the GreetingPlugin'
}
}
}
}
// Apply the plugin
apply plugin: GreetingPlugin
gradle -q hello输出:
> gradle -q hello
Hello from the GreetingPlugin
需要注意的一点是,应用它的每个项目都会创建一个新的插件实例。Plugin是泛型类型,示例使其接收Project类型作为类型参数。插件可以改为接收类型为应用于设置脚本中的Settings或初始化脚本中的Gradle类型的参数。
插件可配置
大多数插件需要从构建脚本中获取一些配置,一种作法是使用扩展对象。Gradle的Project关联一个包含已应用于项目的插件的所有设置和属性ExtensionContainer对象,通过向该容器添加扩展对象来为插件提供配置。扩展对象只是一个Java Bean兼容类。Groovy是实现扩展对象的很好选择,因为普通的旧Groovy对象包含Java Bean所需的所有getter和setter方法,Java和Kotlin也是不错的选择。让我们为项目添加一个简单的扩展对象。这里添加一个可以配置问候语的 greeting
扩展对象。
build.gradle
class GreetingPluginExtension {
String message = 'Hello from GreetingPlugin'
}
class GreetingPlugin implements Plugin<Project> {
void apply(Project project) {
// 添加 'greeting' 扩展对象
def extension = project.extensions.create('greeting', GreetingPluginExtension)
// 添加一个使用扩展对象配置的task
project.task('hello') {
doLast {
println extension.message
}
}
}
}
apply plugin: GreetingPlugin
// 配置扩展
greeting.message = 'Hi from Gradle'
gradle -q hello输出
> gradle -q hello
Hi from Gradle
示例中,GreetingPluginExtension是一个旧的Groovy对象,有一个message的属性。扩展对象被添加到greeting的插件列表中,而后该对象成为项目的一个与扩展对象同名的有效属性。以上面为例,Project中添加一个greeting扩展就相当于给Project配置了一个greeting属性。通常需要在单个插件上指定几个相关属性。Gradle为每个扩展对象添加了一个配置闭包块(Closure block),因此您可以将设置组合在一起。以下示例显示了它的工作原理。
build.gradle
class GreetingPluginExtension {
String message
String greeter
}
...
println "${extension.message} from ${extension.greeter}"
...
apply plugin: GreetingPlugin
// 使用DSL块配置扩展
greeting {
message = 'Hi'
greeter = 'Gradle'
}
gradle -q hello输出
> gradle -q hello
Hi from Gradle
这里在greeting
闭包内将多个设置组合在一起。构建脚本中闭包块名称(greeting
)需要与扩展对象名称匹配。当闭包执行时,扩展对象上的字段将根据标准Groovy闭包委托功能映射到闭包内的变量。
buildSrc工程
复杂的构建逻辑通常封装为自定义任务或二进制插件。自定义任务和插件实现不应该存在于构建脚本中。只要代码不需要在多个独立项目之间共享,就可以非常方便地使用buildSrc实现。目录buildSrc被视为已包含的构建。一旦发现该目录,Gradle会自动编译并测试代码并将其放入构建脚本的classpath中。对于多模块构建,只能有一个buildSrc目录,且必须位于根项目目录下。buildSrc应该优先于脚本插件,因为它更容易维护,重构和测试代码。buildSrc适用于Java和Groovy项目的相同源代码约定,还提供对Gradle API的直接访问。可以在buildSrc下的build.gradle中声明其他依赖项。
$ mkdir gradle-plugin-buildsrc
$ cd gradle-plugin-buildsrc/
$ gradle wrapper --gradle-version 4.6 --distribution-type all #gradle-4.6-all初始化Wrapper
$ ./gradlew init #初始化根工程
$ mkdir buildSrc
$ cd buildSrc/
$ ../gradlew init --type groovy-library #buildSrc初始化为groovy-library
$ rm -r gradle* sett* src/*/*/*.groovy #删除无用文件
添加buildSrc/src/main/groovy/org/example/greeting/GreetingPlugin.groovy
package org.example.greeting
import org.gradle.api.Plugin
import org.gradle.api.Project
class GreetingPlugin implements Plugin<Project> {
@Override
void apply(Project project) {
project.tasks.create("hello", Greeting.class) { task ->
task.setMessage("Hello")
task.setRecipient("World")
}
}
}
添加buildSrc/src/main/groovy/org/example/greeting/Greeting.groovy
package org.example.greeting
import org.gradle.api.DefaultTask
import org.gradle.api.tasks.TaskAction
class Greeting extends DefaultTask {
private String message
private String recipient
String getMessage() { return message }
void setMessage(String message) { this.message = message }
String getRecipient() { return recipient }
void setRecipient(String recipient) { this.recipient = recipient }
@TaskAction
void sayGreeting() {
println "$message, $recipient!"
}
}
根工程build.gradle加入
apply plugin: org.example.greeting.GreetingPlugin
后执行
$ ./gradlew hello
Hello, World!
我这里执行的时候抛出了一个buildSrc下的异常:
Caused by: groovy.lang.GroovyRuntimeException: Conflicting module versions. Module [groovy-all is loaded in version 2.4.12 and you are trying to load version 2.4.13
将
groovy-all
版本改成2.4.12
就可以了另外由于buildSrc工程能被构建任务自动识别, 因此build.gradle中的内容完全可以删除,只保留额外的依赖和配置, 这样也不会有上面版本冲突的问题
分配插件id
大多数情况使用id以其容易记忆会比使用全类名来的方便,还会使构建文件更整洁。下面声明插件的id。
创建buildSrc/src/main/resources/META-INF/gradle-plugins/org.example.greeting.properties
implementation-class=org.example.greeting.GreetingPlugin
Gradle使用这个文件来确定哪个类实现了Plugin接口。文件名去掉.properties
后缀就是插件的id。根工程build.gradle改成
apply plugin: 'org.example.greeting'
或
plugins {
id 'org.example.greeting'
}
注意上面是如何将org.example.greeting.properties
文件映射到插件id的。
独立工程
现在将我们的插件移到一个独立的项目中,可以发布它并与其他人分享。这是一个Groovy项目,它生成一个包含插件类的JAR。这是项目的简单构建脚本。它应用Groovy插件,并将Gradle API添加为编译时依赖项。
$ mv buildSrc plugin
此时plugin
模块不再享有buildSrc
时也有的自动被构建脚本识别、自动添加gradle/groovy等依赖、自动添加到classpath中这些“特权”, 因此这些工作需要插件作者来做。编辑plugin/build.gradle:
-
识别工程
顶级settings.gradle加入
include ':plugin'
-
添加依赖
plugins { id 'groovy' } dependencies { compile gradleApi() compile localGroovy() }
-
分配一个id
如果之前buildSrc中没有分配id这里的作法和buildSrc分配插件id是一样的, 这里重新进行配置。
创建 plugin/src/main/resources/META-INF/gradle-plugins/org.samples.greeting.propertiesimplementation-class=org.example.greeting.GreetingPlugin
到这里还不能使用插件。顶级
build.gradle
要到哪里去加载插件jar包呢? 对了classpath没有配置,还要继续配置。 -
发布插件
发布插件的方式常见的有maven、ivy和本地这三种仓库, 前两个属于中央仓库,提交产品需要审核,这里采用便捷的本地maven仓库。编辑plugin/build.gradle加入plugins { id 'maven' } uploadArchives{ repositories.mavenDeployer{ repository(url:uri('../repo')) //保存到上一级目录的repo下 pom.project{ groupId 'org.gradle' artifactId 'customPlugin' version '1.0' } } }
-
注释掉顶级build.gradle下应用的插件, 根目录执行
./gradlew uploadArchives
执行完毕后工程顶级目录下有个
repo
目录:gradle-plugin-buildsrc/ ├── build.gradle ... ├── plugin ├── repo └── settings.gradle repo/ └── org └── gradle └── customPlugin ├── 1.0 │ ├── customPlugin-1.0.jar │ ├── customPlugin-1.0.jar.md5 │ ├── customPlugin-1.0.jar.sha1 │ ├── customPlugin-1.0.pom │ ├── customPlugin-1.0.pom.md5 │ └── customPlugin-1.0.pom.sha1 ├── maven-metadata.xml ├── maven-metadata.xml.md5 └── maven-metadata.xml.sha1
-
添加插件成品到顶级build.gradle的classpath中
buildscript { repositories { maven { url uri('repo') } } dependencies{ classpath 'org.gradle:customPlugin:1.0' } }
-
新建一个模块来应用插件
$ mkdir test-plugin
顶级build.gradle加入一行
include ':test-plugin'
test-plugin/build.gradle加入:apply plugin:'org.samples.greeting'
执行:./gradlew hello
$ ./gradlew hello Hello, World!