Gradle插件中不能直接用String或int做配置字段,因为它们在配置阶段即被固化求值,无法响应后续动态输入且破坏构建缓存;应使用Property(可变配置项)和Provider(不可变惰性计算结果)实现延迟求值、缓存友好与增量构建支持。

Gradle 插件里为什么不能直接用 String 或 int 做配置字段?
因为 Gradle 的构建生命周期分三阶段(初始化、配置、执行),而用户写的 myPlugin { version = "1.2.0" } 是在配置阶段被解析的,但任务真正执行可能在几秒甚至几分钟后。如果 extension 字段是普通 String,它会在配置阶段就被求值、赋值、固化——一旦上游输入(比如 Git 命令结果、环境变量、另一个 task 的输出)还没准备好,就会得到 null 或默认值,且无法回溯更新。
更糟的是,这种写法会破坏 Configuration Cache(构建缓存):缓存依赖“所有输入可序列化 + 无副作用”,而直接读取 System.getenv() 或调用 new File(...).text 就算副作用,Gradle 会直接拒绝缓存整个构建。
-
Property和Provider是惰性容器,只声明“我将来会提供一个值”,不立即计算 - 它们自带线程安全、可监听、可组合(
.map/.flatMap)、可延迟绑定的能力 - Gradle 能识别它们作为合法输入/输出,自动参与增量构建和缓存校验
Property 和 Provider 的分工怎么选?
Property 是可变的持有者,适合 extension 中定义的用户可配置项;Provider 是不可变的计算结果,适合从其他 Property 或 task 输出派生的值。
- extension 类里该用
RegularFileProperty、Property<string></string>、ListProperty<directory></directory>—— 它们支持@Input/@Output注解,IDE 补全也认 - task 里该用
Provider接收参数:inputs.property("version", project.extensions.getByType(MyExt::class).version) - 不要手动 new
Provider.of("xxx"),要用project.objects.property(String::class)或provider { ... }构造 - 嵌套调用别写
ext.version.get() + "-SNAPSHOT",改用ext.version.map { it + "-SNAPSHOT" },否则提前触发求值
常见错误:为什么我的 Property 配置总不生效?
最典型的现象是:build.gradle 里写了 myPlugin { version = "2.0" },但在 task 里打印 ext.version.get() 却是 null 或空字符串。
- 没在 extension 类里用
@get:Input或对应注解标记 getter(Gradle 8+ 强制要求) - 字段声明成
var version: String? = null,而不是abstract val version: Property<string></string> - 在 plugin 的
apply()方法里直接调用了ext.version.get()—— 此时 DSL 还没解析,必然为空 - 用了 Kotlin 的
by property()委托,但 Gradle 的反射机制不识别这种语法糖,导致无法代理设值
Provider 链式调用在真实场景中怎么落地?
比如你想让插件自动从 git rev-parse --short HEAD 生成版本号,并透传给 task,又不想每次 build 都执行 git 命令(影响增量判断)。
正确做法是把命令封装进一个 task,用 RegularFileProperty 存输出,再用 Provider 懒加载内容:
abstract class GitVersionTask : DefaultTask() {
@get:OutputFile abstract val output: RegularFileProperty
@TaskAction fun run() {
val hash = project.exec {
commandLine("git", "rev-parse", "--short", "HEAD")
standardOutput = output.get().asFile.outputStream()
}
}
}
// 在 extension 里:
abstract val gitVersion: Property<String>
val gitVersionProvider = project.tasks.named<GitVersionTask>("gitVersion").flatMap { it.output.asText }
gitVersion.set(gitVersionProvider)
这样,只有当某个 task 真正需要 gitVersion 时,Gradle 才会检查 gitVersion task 是否已执行、输出文件是否变更,再决定是否重跑或复用缓存 —— 整个链路是响应式、可缓存、可中断的。
真正容易被忽略的是:Provider 不是“一劳永逸”的魔法,它只是把求值时机往后推了一步;如果源头 task 的输入没正确定义(比如漏了 @InputFile),整个链路依然会失效。每层都要对齐 Gradle 的输入契约。


















