Gadle实战(五):扩展 Gradle

1.从零构建插件

Gradle 将插件分为两类:

  • 脚本插件
  • 对象插件

一个脚本插件是一个普通的 Gradle 构建脚本,可以被导入到其他构建脚本中。对象插件可以实现你学到的事情,需要实现 org.gradle.api.Plugin 接口。

对象插件的源代码通常放在 buildSrc 目录下,要么和项目在一起,要么是一个独立的项目,并且以 Jar 包的形式发布。

2. 定制 task 的实现选项

Gradle 中有多种定制 Task 的方式,最简单的一种是把它和构建代码一起放在构建脚本里,这种方式存在之前的文章。当触发一个 Task 时,定义的 Task 会自动编译并添加到 classpath 中

另外一种方式就是将定制的 Task 放到项目根目录下的 buildSrc 目录中,遵从语言插件定义的源代码目录约定。位于 buildSrc 目录下的定制的 task 类,会被所有的项目构建共享,并且在 classpath 中自动可用。为了使定制的 Task 在多个项目中共享,可以将它们打成 jar 包,然后在构建脚本中的 classpath 中定义。

以下为定义 Task 的不同实现方式:

2.1 在脚本中定制 Task

Gradle 提供了一个可以通过继承的默认实现:org.gradle.api.DefaultTask

1
2
3
class customTask implements DefaultTask{
xxxx
}

在 buildSrc 中定制 Task

以上方式2,在 buildSrc 中创建定制 Task 的源文件,这是后期通过对象插件使用它们的最好方式。

用 groovy 定义 Task,所有的定制 Task 类放在 ${project_rootDir}/buildSrc/src/main/groovy/项目目录/ 下。

使用定制 Task:

1
2
3
4
5
6
//导入定制 Task
import xxx.xxxx.xxxx.CustomeTask
task test(type:CustomeTask){
xxx
xxx
}

3. 使用和构建对象插件

打包定制的 Task 实现为 Jar 的方式的优缺点,具体参见 Gradle in Action 一书。

对象插件 可以灵活去封装高度复杂的逻辑,并且提供强大的扩展机制可以在构建脚本中定制它的行为。和定制 Task 一样,可以完全访问 Gradle 的公共 API 和工程模型。

Gradle 提供了开箱即用的插件,称为 标准插件,也可以通过 第三方插件 进行扩展。许多插件都是自包含的,这意味着它们要么依赖 Gradle 的核心 API,要么通过包装代码提供功能。更复杂的插件依赖于其他类库、工具或者插件提供的特性。

下图展示了插件在 Gradle 架构的位置:

Gradle 插件架构

在平常的开发中,我们经常使用 Java 插件来扩展项目功能。如下图 Java 插件特性所示,插件可以提供一个 Task 集合,并且整合到执行生命周期中,引入新的项目布局并提供有意义的默认值,添加属性来定制化它的行为,给依赖管理暴露对应的配置(比如平时使用到的 compile)。

Java 插件特性

通过一行代码引入 Java 插件,就可以使用 Java 插件相应的功能来编译源代码、运行单元测试、生成报告,并将项目打包为Jar

标准插件也提供了很多通用功能,大部分能够满足开发者的需求。由社区或这开源组织开发的三方插件,可以用来给构建脚本增强非标准功能。

3.1 使用对象插件

在项目中同通过 apply 方法来配置项目,从而使用标准插件,该方式是 Project 对象提供的方式,存在一个类型为 Map 的参数 options。

标准插件的便利在于他们是 Gradle 运行时的一部分,用户不需要知道插件所依赖的类库,这些类库的位置为 Gradle 安装目录的 libs/plugins 下。

3.1.1 通过名字使用插件

插件标识符一个简短的名字,通过插件的元信息提供,在项目中使用 Java 插件,直接传入键值对 plugin:’java‘:

1
apply plugins: 'java'

3.1.2 通过类型使用插件

如果插件没有暴露名字,或者两个插件的命名冲突,那么可以类型来使用插件。

1
apply plugin:org.gradle.api.plugins.JavaPlugin

3.1.3 使用外部插件

构建脚本并不知道外部插件的存在,需要将它放到 classpath 下。可以通过 buildScript 方法来做这件事,它定义了外部插件的位置、仓库和插件依赖。

在配置阶段,Gradle 中构建项目模型,连接插件构建逻辑。一旦插件下载完成,就会放置在本地缓存中,以便后续的运行可以使用到它们。

以下展示如何使用 MavenCentral 中的 tomcat 插件

1
2
3
4
5
6
7
8
9
10
buildScript{
repostories{
// 该插件的原始仓库
mavenCentral()
}
dependencies{
// 定义插件依赖
classpath 'org.gradle.api.plugins:gradle-tomcat-plugin:0.9.7'
}
}

4. 解析对象插件

以下图片显示了实现一个插件的几种选择:

对于实现一个对象插件,有四个元素是十分重要的:

  • 放置插件实现的位置

Gradle 在这方式十分灵活,代码可以发在构建脚本中,可以放在 buildSrc 目录下,也可以作为一个 独立的工程 被开发并且以 Jar 包的形式发布。

  • 每一个插件都需要提供一个实现类

该实现类代表插件的入口,插件可以用任何 JVM 语言编写并编译成字节码,比如 Java、Kotlin、Groovy 等。

  • 插件通过暴露出来的扩展对象进行定制

当用户想要在构建脚本中覆盖插件的默认配置,这是十分重要的实现方式。

  • 插件描述符

插件描述符是一个属性文件,它包含类插件的元信息,通常包含插件的简短名字和插件实现类的映射。

5. 编写对象插件并运用到项目

编写一个插件的最低要求是提供 org.gradle.api.Plugin 接口的一个实现类,该接口仅有一个方法:apply(Project)。

5.1 通过 buildSrc 的形式编写对象插件

使用 buildSrc 的方式定义对象插件的好处是,在早期开发插件阶段,不需要打包插件代码,可以得到一个快速的反馈,能够让开发者能够专心通过 Gradle Api 实现业务逻辑。

buildSrc 工程下的指定包目录中创建一个插件的实现类,

1
2
3
4
5
6
class CustomPlugin implements Plugin<Project>{
@Override
void apply(Project project){
xxx
}
}

想要在项目中使用该插件,则在 build.gradle 中使用插件的实现类型:

1
apply plugin: xxxx.xxx.CustomPlugin

6. 插件扩展机制

通过 —P 和 —D 可以在执行 Gradle 命令行时提供参数,为 Task 提供输入,但这种方式不总是可取的。

Gradle 允许通过暴露一个带有唯一命名空间的 DSL 来建立自己的构建语言,下面展示一个名为 cloudBees 的闭包,允许从构建脚本中给 task 所需要的属性设置值。

1
2
3
4
cloudBees{
apiUrl = 'https://xxx'
apiKey = project.apiKey
}

Gradle 会将语言结构模型化为扩展,扩展是可以被添加到 Gradle 对象中,比如 Project或者Task。

如果一个类实现了 org.gradle.api.plugins.ExtensionAware 接口,就认为它是可扩展的,每种扩展都是一种数据结构,它是扩展的基础。

以下为 CloudBees 插件的扩展模型:

1
2
3
4
5
package xxxx.xxx
class CloudBeesPluginExtension{
String apiUrl
String apiKey
}

7. 注册和使用扩展

7.1 给插件一个有意义的名字

默认情况下,插件的名字从实现了 org.gradle.api.Plugin 接口的全限定类名继承而来。

对于对象插件来说,可以在 META-INF/gradle-plugins 目录下的一个属性文件中配置名字,该属性文件的名字自动决定了插件的名字,比如 META-INF/gradle-plugins/nuwa.properties 暴露插件的名字是 nuwa,在 nuwa.properties 中
需要将类的全限定类名赋值给 implementation-class,如下:

1
implementation-class=com.xxx.xxx.NuwaPlugin

在构建脚本 build.gradle 中使用这个插件:

1
apply plugin 'nuwa'

7.2 测试对象插件

具体查看 Gradle in action。

8.开发和使用独立的对象插件

如果想要在主构建的脚本中使用插件,那么在 buildSrc 项目中实现一个插件时是十分方便的;但是如果想要在多个模块中的共享插件,那么需要将插件作为独立的项目开发,然后将插件发布到仓库中。

8.1 项目和仓库配置

新建一个独立 module,将在 buildSrc 中的所有代码移到该 module 中。每次想要发布新的插件版本时,所产生的 Jar 文件会被发布到名为 repo 的与项目同目录的本地 maven 库中。

书中例子:假设其他项目想要使用该插件,那么在该项目的构建脚本中定义本地仓库,声明插件作为依赖,使用插件中的 Task 与 CloudBees 后端服务交互。

其中 plugin 为定义插件的模块。

8.2 构建插件项目

  • 定义依赖

此时不能再访问 buildSrc 基础设施,所以需要定义对 Groovy 和 Gradle API 类库的依赖。

  • 发布插件

通过 Maven 插件,可以为插件生成的 POM 文件和将插件发布到 Maven 库中。配置 Maven 部署器将 POM 和 插件上传到本地目录中,为了更好的管理插件的版本,需要为插件指定 group、name\version.

在插件被使用之前,需要先执行 gradle uploadArchives 命令,使用 Maven 插件相关 task 帮助下上传该插件。

发布插件后,在根目录下出现名为 repo 的新目录,包含了插件的 POM 文件和 Jar 文件。

  • 在项目中使用插件

在项目中构建脚本中添加如下配置:

对本地 Maven 库的声明也可以使用相对路径:

1
2
3
4
5
6
7
8
9
buildscript {
repositories {
jcenter()
maven {
url uri('./repo')
}
}
....
}