Gradle插件探究
1. 前言
1.1 什么是Gradle Plugin
Gradle插件(Plugin)是一种用于扩展和定制 Gradle构建系统功能的机制。上一次分享我们说到,Gradle是Android官方推荐的构建工具,是一种开源构建自动化工具,支持多种项目、多模块,可以构建几乎任何类型的软件。插件为Gradle提供了灵活性,允许开发者根据特定需求添加自定义行为和功能。通过编写自己的Gradle插件,你可以定制和扩展 Gradle 构建系统,以适应特定项目的需求。你可以在插件中义自定义任务、配置扩展、操作项目属性、应用其他插件等。插件使得构建过程变得可控和可定制,从而提高开发效率。
1.2 使用Gradle Plugin的意义
我们可以在构建脚本中通过添加自己的task的方式来自定义构建过程,这种方式是比较原始的,很沉重。Gradle官方推荐使用插件,而不是向构建脚本添加逻辑来扩展项目,以此来提高复用性和可读性。
- 逻辑复用:将相同的逻辑封装起来供多个项目使用,减少重复维护的开销。
- 代码可读性:将逻辑代码从其他代码中抽离出来,提高代码可读性。
1.3 插件类型和使用
Gradle中有两种常见的插件类型——二进制插件和脚本插件。
- 二进制插件:通过实现Plugin接口以编程的方式编写,也可以使用Gradle的DSL语言以声明的方式编写。二进制插件可以驻留在构建脚本中、项目层次接口中或外部插件的jar包中。可以通过插件id来应用插件。
- 脚本插件:是附加的构建脚本,可以存在本地,也可以存在网络上。远程脚本位置通过HTTP URL指定,version.gradle就是一个脚本插件,通过URL远程访问。
2. 自定义插件
插件可以写在三个地方:构建脚本、buildSrc项目、单独项目(以下代码示例基于Gradle8.2编写)
2.1 构建脚本
写在build.gradle中,作用域为当前的Project。
class MyPlugin : Plugin<Project> {
override fun apply(target: Project) {
println("===Hello,${this.javaClass.name}!===")
}
}
// 调用插件
apply<MyPlugin>()
2.2 buildSrc项目
buildSrc是Gradle默认的插件模块,会被自动识别为参与构建的模块,不需要在settings.gradle中使用include引入,若引入会报错:‘buildSrc’ cannot be used as a project name as it is a reserved name;buildSrc会自动被添加到构建脚本的classpath中,无需手动添加;
buildSrc文件夹目录如下:
buildSrc插件没有默认的插件ID,你可以通过插件的完全限定类名来应用插件。也可以为buildSrc插件定义一个插件ID。
// 在buildSrc的build.gradle中定义插件名
gradlePlugin{
plugins {
create("myplugin2") {
id = "myplugin2"
implementationClass = "MyPlugin2"
}
}
}
2.3 单独的插件项目
2.3.1 插件目录结构初始化
新建一个工程,在工程中添加一个module,目录如下:
其中MyPlugin3是Plugin的实现类,重写了apply方法,并在其中添加插件逻辑。
class MyPlugin3 : Plugin<Project> {
override fun apply(target: Project) {
println("This is MyPlugin3")
}
}
2.3.2 插件配置
- Gradle6.4之前:插件ID配置使用src/main/resources/META-INF/gradle-plugins/xxx.properties的方法配置。(properties文件名与插件ID匹配,properties文件中的implementation-class属性标识插件的实现类)插件信息在gradle.properties中配置。
implementation-class=com.vesync.version.VersionPlugin
- Gradle 6.4及以后:在插件目录下的build.gradle.kts中使用gradlePlugin{}块,Gradle会自动在 jar 包的 META-INF 目录中生成插件描述符。引入java-gradle-plugin插件,Gradle6.4之后不需要再添加gradleApi()来配置Plugin的依赖,它会自动引入java、gradleApi()。示例如下:
plugins {
id("java-gradle-plugin")//会自动引入java-library、gradleApi()
id("org.jetbrains.kotlin.jvm") //支持kotlin编写插件
id("maven-publish")// 发布到maven仓库
}
gradlePlugin {
plugins {
create("myplugin3") {
group = "com.vesync"
version = "1.0.0"
id = "myplugin3"
implementationClass = "com.vesync.myplugin.MyPlugin3"
}
}
}
2.3.3 插件发布
在插件目录下的build.gradle中添加发布依赖,配置发布信息。我们这里只发布到本地repo目录下。
publishing {
// 配置仓库地址
repositories {
maven {
url = uri("../repo")
}
}
}
发布命令:./gradlew publish
2.3.4 插件应用
这里直接使用插件Id来应用插件,我们也可以将插件的版本信息写入libs.versions.toml。
在settings.gradle.kts中配置插件仓库地址:
pluginManagement {
// 配置仓库地址
repositories {
google()
mavenCentral()
gradlePluginPortal()
maven { setUrl("/repo") }
}
}
在根目录下的build.gradle.kts中通过classpath引入插件依赖:
buildscript {
dependencies {
classpath("com.vesync:myplugin3:1.0.0")
}
}
在app目录下的build.gradle.kts中通过implementation引入插件依赖,并应用插件:
plugins {
alias(libs.plugins.androidApplication)
alias(libs.plugins.kotlinAndroid)
id("myplugin3")
}
dependencies {
implementation("com.vesync:myplugin3:1.0.0")
}
2.4 插件扩展机制
在自定义插件的时候有自定义配置的需求,例如我们可以在android{}中配置compileSdk、buildToolsVersion等信息。
Project有一个关联的ExtensionContainer,其中包含了已应用插件的所有设置和属性。扩展对象只是一个对外开放的Java Bean或GroovyBean。
2.4.1 使用步骤
- 首先定义一个扩展配置类(不可为final),属性即为可配置的项:
open class Plugin2Extension(project: Project) {
var namespace = ""
companion object {
const val NAME = "plugin2Config"
}
}
- 然后在插件类中将扩展对象添加到Project的ExtensionContainer中:
class MyPlugin2 : Plugin<Project> {
override fun apply(project: Project) {
project.extensions.create(Plugin2Extension.NAME, Plugin2Extension::class.java)
}
}
- 通过扩展对象名获取到扩展对象即扩展属性:
project.afterEvaluate {
val plugin2Extension = project.extensions.getByName(Plugin2Extension.NAME) as Plugin2Extension
println(plugin2Extension.namespace)
}
根据Gradle生命周期可知,在配置阶段会下载所有插件和构建脚本依赖,执行构建脚本。扩展配置代码的执行时机晚于插件apply方法的执行时机,插件内部无法正确的获取到配置,因此要将插件扩展的使用放在afterEvaluate回调中,此时插件代码已执行。
2.4.2 扩展属性嵌套
在扩展类中组合另一个配置类的操作我们角嵌套扩展。例如android{}中嵌套了defaultConfig{},defaultConfig{}里面接着嵌套。。。
- 方案一:扩展类实例化时将嵌套的扩展对象添加到ExtensionContainer中去:
open class Plugin2Extension(project: Project) {
var namespace = ""
val defaultConfig: DefaultConfig =
project.extensions.create(DefaultConfig.NAME, DefaultConfig::class.java)
companion object {
const val NAME = "plugin2Config"
}
}
open class DefaultConfig {
var applicationId = ""
companion object {
const val NAME = "defaultConfig"
}
}
外部使用插件的配置:
plugin2Config {
namespace = "gradle-plugin-demo"
defaultConfig {
applicationId = "gradle-plugin-demo"
}
}
- 方案二:packaing方法接受一个闭包,此闭包是对PackConfig的一个扩展。android{}扩展使用的是这种方式。
open class Plugin2Extension2 {
val packConfig: PackConfig = PackConfig()
fun packaging(action: PackConfig.() -> Unit) {
action.invoke(packConfig)
}
companion object {
const val NAME = "plugin2Config2"
}
}
open class PackConfig {
var exclude = ""
}
外部使用插件的配置:
plugin2Config2 {
packaging {
exclude = "xxxxx"
}
}
- 方案三:命名领域对象容器NamedDomainObjectContainer是一个支持配置不固定数量配置的容器。我们平时用到的构建类型配置buildTypes{},除了默认提供的release和debug,还可以自定义构建类型,这种类型的配置就是通过NamedDomainObjectContainer实现的。可以通过ObjectFactory.domainObjectContainer(Class)来获取实例。
注意:这里的BuildTypeConfig类必须带有name(String)属性和包含name的构造函数。
open class Plugin2Extension3(objectFactory: ObjectFactory) {
val buildTypeConfigT = BuildTypeConfig()
val buildTypeConfig: NamedDomainObjectContainer<BuildTypeConfig> =
objectFactory.domainObjectContainer(BuildTypeConfig::class.java)
fun buildTypeConfig(action: NamedDomainObjectContainer<BuildTypeConfig>.() -> Unit) {
buildTypeConfig.create("debug")
buildTypeConfig.create("release")
action.invoke(buildTypeConfig)
}
fun NamedDomainObjectContainer<BuildTypeConfig>.debug(action: BuildTypeConfig.() -> Unit) {
action.invoke(buildTypeConfigT)
}
fun NamedDomainObjectContainer<BuildTypeConfig>.release(action: BuildTypeConfig.() -> Unit) {
action.invoke(buildTypeConfigT)
}
companion object {
const val NAME = "plugin2Config3"
}
}
open class BuildTypeConfig(name: String? = null) {
val name = name
var value = ""
}
外部使用插件配置:
plugin2Config3 {
buildTypeConfig {
debug {
value = "buildTypeConfig-debug"
}
release {
value = "buildTypeConfig-release"
}
create("other") {
value = "buildTypeConfig-other"
}
}
}
3. 文献参考
- Android进阶之Gradle插件开发指南
- 手把手带你自定义 Gradle 插件 —— Gradle 系列(2)
- Developing Custom Gradle Plugins
- Using Gradle Plugins
欢迎讨论!