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. 文献参考

欢迎讨论!

  • 20
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值