Gradle插件任务在配置缓存下失败是因为违反纯函数式要求:读取未声明外部状态、修改project对象、使用硬编码路径或非Provider声明inputs/outputs、自定义task含不可序列化字段、Extension配置未与task输入对齐。

Gradle插件任务为什么在配置缓存下失败
配置缓存要求构建逻辑是纯函数式的:不能读取未声明的外部状态,不能修改 project 对象,也不能依赖运行时环境变量或时间戳。插件任务一旦在 configure 阶段做了以下任意操作,就会直接触发 Configuration cache is not supported 错误:
- 调用
project.afterEvaluate或gradle.taskGraph.whenReady - 使用
System.getenv()、new Date()、Math.random() - 通过字符串拼接构造路径,比如
"$project.buildDir/classes" - 在 task 配置闭包里动态修改
inputs或outputs的值(未用Provider延迟求值)
task.inputs/outputs 必须用 Provider 声明
Gradle 8.5+ 要求所有输入输出必须通过 Provider 延迟绑定,否则配置缓存无法序列化其依赖关系。常见错误是直接赋值字符串或 File 对象:
// ❌ 错误:硬编码路径,无法参与缓存键计算
inputs.file("src/main/resources/config.json")
outputs.dir("build/myplugin")
// ✅ 正确:用 layout 和 providers 显式声明
inputs.file(layout.projectDirectory.file("src/main/resources/config.json"))
outputs.dir(layout.buildDirectory.dir("myplugin"))
关键点:
- 永远用
layout.projectDirectory/layout.buildDirectory替代project.projectDir/project.buildDir - 路径拼接必须用
.dir("xxx")或.file("xxx"),不能用字符串 + 运算符 - 若需动态路径(如基于版本号),用
providers.gradleProperty("version")或providers.systemProperty("os.name")
自定义 task 类必须满足序列化约束
配置缓存会将 task 实例完整序列化并复用,所以你的 task 类不能含不可序列化字段(如 Logger、Project、FileTree),也不能在构造器或 @Inject 中做副作用操作。
正确写法要点:
- 继承
DefaultTask,字段全部用@Input/@OutputDirectory等注解标记 - 移除所有
project.getLogger(),改用getLogger()(由 Gradle 注入) - 不要在
init块或构造器中读文件、解析 JSON、初始化 SDK 客户端 - 把耗时或非确定性逻辑移到
@TaskAction内——它不参与配置缓存,只影响执行阶段
Extension 配置必须与 task inputs 对齐
如果你的插件暴露了 myPlugin { outputDir = "build/custom" } 这类 DSL,那这个 outputDir 值必须最终作为 task 的 @OutputDirectory 输入进去,否则缓存键不包含该配置,会导致行为不一致。
典型陷阱:
- Extension 里用
String outputDir字段接收值,但 task 里没把它传进outputs.dir(...) - Extension 用了
mutableListOf存配置项,而配置缓存要求所有集合为不可变类型(listOf/setOf) - Kotlin DSL 中写了
enableLogging.set(true),但 task 没监听这个Provider做条件分支
最稳妥做法:Extension 所有属性都声明为 Property 或 Provider,并在 task 构造时直接传入,不中间转换。
配置缓存不是“开了就快”,而是“开了就必须守规矩”。最容易被忽略的是:哪怕你只在一个 task 里用了 System.currentTimeMillis(),整个构建都会退回到无缓存模式——而且不会报错,只会静默失效。











