插件开发基础
源:Gradle 官方文档 - Developing Custom Gradle Plugins
学习如何开发自定义 Gradle 插件,封装复杂的构建逻辑。
插件形态
三种插件形式
| 形态 | 位置 | 适用场景 | 发布方式 |
|---|---|---|---|
| 脚本插件 | 任意.gradle.kts | 极简单逻辑 | apply from |
| buildSrc | buildSrc/ | 单项目内部 | 自动识别 |
| 独立插件 | 独立项目 | 跨项目共享 | 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"
}
}
}