如何将Closure或Gradle Action作为输入传递给自定义Gradle任务?
问题分析
- 类型不匹配:自定义任务中
renamer声明为Property<Action<Path>>,但实际使用时传入的是(String) -> String类型的闭包,参数类型完全不兼容。 - 序列化失败:
Action接口未继承Serializable,而@CacheableTask要求所有@Input标记的属性必须可序列化,否则任务缓存逻辑会直接报错。
解决方案
方案1:使用Gradle内置Transformer接口(推荐)
Gradle提供的Transformer<T, U>是原生支持序列化的函数式接口,完美适配“输入转换为输出”的重命名场景,且能被Gradle缓存机制正确识别。
修改自定义任务代码:
@CacheableTask abstract class FooTask : DefaultTask() { @get:InputFile abstract val input: RegularFileProperty @get:Input abstract val renamer: Property<Transformer<String, Path>> @TaskAction fun generate() { val path = input.get().asFile.toPath() val newName = renamer.get().transform(path) logger.quiet("original: $path, final: $newName") } }
注意:将
input的@Input改为@InputFile,这是Gradle处理文件类输入的标准注解,比通用@Input更精准。
在build.gradle.kts中调用:
tasks { register<FooTask>("foo") { group = "foo" input.set(layout.projectDirectory.file("test.bar")) renamer.set { path: Path -> path.fileName.toString().replace(Regex("(.*)\\.bar"), "$1.baz") } } }
方案2:自定义可序列化函数接口
如果需要完全贴合原有逻辑结构,可以自行定义继承Serializable的函数式接口:
interface SerializablePathRenamer : (Path) -> String, Serializable @CacheableTask abstract class FooTask : DefaultTask() { @get:InputFile abstract val input: RegularFileProperty @get:Input abstract val renamer: Property<SerializablePathRenamer> @TaskAction fun generate() { val path = input.get().asFile.toPath() val newName = renamer.get().invoke(path) logger.quiet("original: $path, final: $newName") } }
调用时直接传递Kotlin闭包即可,Gradle会自动将其适配为可序列化的实现:
tasks { register<FooTask>("foo") { group = "foo" input.set(layout.projectDirectory.file("test.bar")) renamer.set { path -> path.fileName.toString().replace(Regex("(.*)\\.bar"), "$1.baz") } } }
关于Copy任务的实现逻辑
Gradle内置的Copy任务对用户自定义闭包做了特殊处理:
- 内部用可序列化的包装类封装用户传入的闭包
- 计算缓存哈希时,会解析闭包的代码内容(而非仅依赖对象序列化),确保逻辑变更时能正确失效缓存
内容的提问来源于stack exchange,提问作者phreed
相关产品推荐
相关产品推荐

