Skip to content

插件开发基础

源:Gradle 官方文档 - Developing Custom Gradle Plugins

学习如何开发自定义 Gradle 插件,封装复杂的构建逻辑。

插件形态

三种插件形式

形态位置适用场景发布方式
脚本插件任意.gradle.kts极简单逻辑apply from
b​uildSrcbuildSrc/单项目内部自动识别
独立插件独立项目跨项目共享Maven/复合构建

脚本插件

common.gradle.kts

kotlin
// 通用配置
allprojects {
    repositories {
        google()
        mavenCentral()
    }
}

使用

kotlin
// build.gradle.kts
apply(from = "common.gradle.kts")

buildSrc 插件

buildSrc/src/main/kotlin/CommonPlugin.kt

kotlin
import org.gradle.api.Plugin
import org.gradle.api.Project

class CommonPlugin : Plugin<Project> {
    override fun apply(target: Project) {
        // 配置逻辑
    }
}

使用

kotlin
// build.gradle.kts
plugins {
    id("CommonPlugin")  // 自动识别
}

独立插件(推荐)

使用复合构建,参考约定插件章节。


Plugin 接口

基本结构

kotlin
import org.gradle.api.Plugin
import org.gradle.api.Project

class MyPlugin : Plugin<Project> {
    override fun apply(target: Project) {
        // target 就是应用插件的 Project 对象
    }
}

apply 方法

参数

  • target: Project:应用插件的项目
  • 可以访问:
    • target.extensions:扩展
    • target.tasks:任务
    • target.dependencies:依赖
    • target.plugins:其他插件

创建扩展

为什么需要扩展

扩展:允许用户配置插件行为。

示例

kotlin
// 插件提供
greeting {
    message = "Hello"
    name = "World"
}

定义扩展接口

kotlin
interface GreetingExtension {
    val message: Property<String>
    val name: Property<String>
}

注册扩展

kotlin
class GreetingPlugin : Plugin<Project> {
    override fun apply(target: Project) {
        // 创建扩展
        val extension = target.extensions.create<GreetingExtension>("greeting")
        
        // 设置默认值
        extension.message.convention("Hello")
        extension.name.convention("World")
        
        // 注册任务
        target.tasks.register("greet") {
            doLast {
                println("${extension.message.get()}, ${extension.name.get()}!")
            }
        }
    }
}

使用扩展

kotlin
// build.gradle.kts
plugins {
    id("my.greeting")
}

greeting {
    message.set("Hi")
    name.set("Gradle")
}

输出

bash
$ ./gradlew greet
Hi, Gradle!

注册任务

基本任务注册

kotlin
class MyPlugin : Plugin<Project> {
    override fun apply(target: Project) {
        target.tasks.register("myTask") {
            group = "custom"
            description = "My custom task"
            
            doLast {
                println("Running my task")
            }
        }
    }
}

类型化任务

kotlin
abstract class GreetTask : DefaultTask() {
    @get:Input
    abstract val message: Property<String>
    
    @TaskAction
    fun greet() {
        println(message.get())
    }
}

class MyPlugin : Plugin<Project> {
    override fun apply(target: Project) {
        target.tasks.register<GreetTask>("greet") {
            message.set("Hello from task")
        }
    }
}

应用其他插件

pluginManager API

kotlin
class MyPlugin : Plugin<Project> {
    override fun apply(target: Project) {
        with(target.pluginManager) {
            apply("com.android.library")
            apply("org.jetbrains.kotlin.android")
        }
    }
}

检查插件是否已应用

kotlin
class MyPlugin : Plugin<Project> {
    override fun apply(target: Project) {
        if (target.pluginManager.hasPlugin("com.android.library")) {
            // Android Library 特定配置
        }
    }
}

在插件应用后执行

kotlin
class MyPlugin : Plugin<Project> {
    override fun apply(target: Project) {
        target.pluginManager.withPlugin("com.android.library") {
            // 当 Android Library 插件应用后执行
            target.extensions.configure<LibraryExtension> {
                // 配置
            }
        }
    }
}

配置 Android

配置 Library

kotlin
import com.android.build.api.dsl.LibraryExtension

class AndroidLibraryPlugin : Plugin<Project> {
    override fun apply(target: Project) {
        with(target) {
            pluginManager.apply("com.android.library")
            
            extensions.configure<LibraryExtension> {
                compileSdk = 34
                
                defaultConfig {
                    minSdk = 24
                }
                
                compileOptions {
                    sourceCompatibility = JavaVersion.VERSION_17
                    targetCompatibility = JavaVersion.VERSION_17
                }
            }
        }
    }
}

配置 Application

kotlin
import com.android.build.api.dsl.ApplicationExtension

class AndroidAppPlugin : Plugin<Project> {
    override fun apply(target: Project) {
        with(target) {
            pluginManager.apply("com.android.application")
            
            extensions.configure<ApplicationExtension> {
                compileSdk = 34
                
                defaultConfig {
                    targetSdk = 34
                    minSdk = 24
                    
                    versionCode = 1
                    versionName = "1.0.0"
                }
            }
        }
    }
}

通用配置

kotlin
import com.android.build.api.dsl.CommonExtension

fun Project.configureAndroid(
    commonExtension: CommonExtension<*, *, *, *, *, *>
) {
    commonExtension.apply {
        compileSdk = 34
        
        defaultConfig {
            minSdk = 24
        }
        
        compileOptions {
            sourceCompatibility = JavaVersion.VERSION_17
            targetCompatibility = JavaVersion.VERSION_17
        }
    }
}

添加依赖

在插件中添加依赖

kotlin
class MyPlugin : Plugin<Project> {
    override fun apply(target: Project) {
        target.dependencies {
            add("implementation", "androidx.core:core-ktx:1.12.0")
            add("testImplementation", "junit:junit:4.13.2")
        }
    }
}

使用 Version Catalog

kotlin
class MyPlugin : Plugin<Project> {
    override fun apply(target: Project) {
        val libs = target.extensions.getByType<VersionCatalogsExtension>().named("libs")
        
        target.dependencies {
            add("implementation", libs.findLibrary("androidx.core.ktx").get())
            add("testImplementation", libs.findLibrary("junit").get())
        }
    }
}

发布插件

发布到 Maven Local

build.gradle.kts

kotlin
plugins {
    `kotlin-dsl`
    `maven-publish`
}

group = "com.example"
version = "1.0.0"

publishing {
    publications {
        create<MavenPublication>("maven") {
            from(components["java"])
        }
    }
}

发布

bash
./gradlew publishToMavenLocal

发布到 Maven Central

build.gradle.kts

kotlin
plugins {
    `kotlin-dsl`
    `maven-publish`
    signing
}

publishing {
    publications {
        create<MavenPublication>("maven") {
            from(components["java"])
            
            pom {
                name.set("My Gradle Plugin")
                description.set("A custom Gradle plugin")
                url.set("https://github.com/example/my-plugin")
                
                licenses {
                    license {
                        name.set("The Apache License, Version 2.0")
                        url.set("http://www.apache.org/licenses/LICENSE-2.0.txt")
                    }
                }
            }
        }
    }
    
    repositories {
        maven {
            url = uri("https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/")
            credentials {
                username = findProperty("ossrhUsername") as String?
                password = findProperty("ossrhPassword") as String?
            }
        }
    }
}

signing {
    sign(publishing.publications["maven"])
}

测试插件

使用 TestKit

添加依赖

kotlin
dependencies {
    testImplementation(gradleTestKit())
    testImplementation("junit:junit:4.13.2")
}

编写测试

kotlin
import org.gradle.testkit.runner.GradleRunner
import org.junit.Test
import java.io.File

class MyPluginTest {
    @Test
    fun testPlugin() {
        // 创建临时项目
        val projectDir = File("build/test-project")
        projectDir.mkdirs()
        
        File(projectDir, "settings.gradle.kts").writeText("""
            rootProject.name = "test"
        """.trimIndent())
        
        File(projectDir, "build.gradle.kts").writeText("""
            plugins {
                id("my.plugin")
            }
        """.trimIndent())
        
        // 运行构建
        val result = GradleRunner.create()
            .withProjectDir(projectDir)
            .withArguments("myTask")
            .withPluginClasspath()
            .build()
        
        // 断言
        assert(result.output.contains("Running my task"))
    }
}

实战案例

案例1:版本号管理插件

kotlin
class VersionPlugin : Plugin<Project> {
    override fun apply(target: Project) {
        val extension = target.extensions.create<VersionExtension>("versionConfig")
        
        target.tasks.register("printVersion") {
            doLast {
                val major = extension.major.get()
                val minor = extension.minor.get()
                val patch = extension.patch.get()
                println("Version: $major.$minor.$patch")
            }
        }
        
        target.afterEvaluate {
            if (target.pluginManager.hasPlugin("com.android.application")) {
                target.extensions.configure<ApplicationExtension> {
                    defaultConfig {
                        val versionCode = extension.major.get() * 10000 + 
                                        extension.minor.get() * 100 + 
                                        extension.patch.get()
                        this.versionCode = versionCode
                        this.versionName = "${extension.major.get()}.${extension.minor.get()}.${extension.patch.get()}"
                    }
                }
            }
        }
    }
}

interface VersionExtension {
    val major: Property<Int>
    val minor: Property<Int>
    val patch: Property<Int>
}

使用

kotlin
plugins {
    id("my.version")
}

versionConfig {
    major.set(1)
    minor.set(2)
    patch.set(3)
}

案例2:代码生成插件

kotlin
abstract class GenerateCodeTask : DefaultTask() {
    @get:Input
    abstract val packageName: Property<String>
    
    @get:OutputDirectory
    abstract val outputDir: DirectoryProperty
    
    @TaskAction
    fun generate() {
        val pkg = packageName.get()
        val file = outputDir.file("Generated.kt").get().asFile
        
        file.parentFile.mkdirs()
        file.writeText("""
            package $pkg
            
            object Generated {
                const val TIMESTAMP = ${System.currentTimeMillis()}L
            }
        """.trimIndent())
    }
}

class CodeGenPlugin : Plugin<Project> {
    override fun apply(target: Project) {
        val extension = target.extensions.create<CodeGenExtension>("codeGen")
        
        val generateTask = target.tasks.register<GenerateCodeTask>("generateCode") {
            packageName.set(extension.packageName)
            outputDir.set(target.layout.buildDirectory.dir("generated/kotlin"))
        }
        
        target.pluginManager.withPlugin("com.android.application") {
            target.extensions.configure<ApplicationExtension> {
                sourceSets.getByName("main").java.srcDir(generateTask)
            }
        }
    }
}

interface CodeGenExtension {
    val packageName: Property<String>
}

最佳实践

使用 Provider API

kotlin
val extension = extensions.create<MyExtension>("myConfig")
extension.value.convention("default")  // 设置默认值

afterEvaluate 谨慎使用

kotlin
// ❌ 避免
afterEvaluate {
    // 会破坏配置缓存
}

// ✅ 推荐
pluginManager.withPlugin("com.android.application") {
    // 插件应用后执行
}

插件ID命名

kotlin
// 使用反向域名
id = "com.example.my-plugin"
id = "org.company.android.library"

文档注释

kotlin
/**
 * 配置 Android 库的通用设置
 */
class AndroidLibraryPlugin : Plugin<Project> {
    // ...
}

版本兼容性

kotlin
// 声明最低 Gradle 版本
tasks.withType<ValidatePlugins>().configureEach {
    enableStricterValidation.set(true)
}

错误处理

kotlin
class MyPlugin : Plugin<Project> {
    override fun apply(target: Project) {
        require(target.pluginManager.hasPlugin("java")) {
            "MyPlugin requires Java plugin"
        }
    }
}