Gradle 增量构建与构建缓存:自定义 Task 如何实现 “只构建变化内容”?

要实现自定义 Gradle Task 的"只构建变化内容"(即增量构建),核心是通过输入(Inputs)和输出(Outputs)的声明,让 Gradle 能够追踪任务的依赖和结果变化。结合构建缓存(Build Cache),还能实现跨机器/跨构建的结果复用。以下是具体实现步骤和原理:

一、核心原理:输入输出追踪

Gradle 增量构建的核心逻辑是:

  • 若 Task 的输入未发生变化 ,且输出未被修改或删除,则跳过该任务(UP-TO-DATE)。
  • 若输入变化或输出丢失,才重新执行任务。

因此,自定义 Task 需明确声明:

  • 输入(Inputs):影响任务结果的所有因素(如文件、参数、依赖等)。
  • 输出(Outputs):任务生成的所有结果(如文件、目录等)。

二、自定义 Task 实现增量构建

1. 基础实现(继承 DefaultTask)

自定义 Task 需继承 DefaultTask,并通过注解声明输入输出。

groovy 复制代码
import org.gradle.api.DefaultTask
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.OutputDirectory
import org.gradle.api.tasks.TaskAction

// 自定义任务:将输入文本写入输出目录的文件
abstract class MyIncrementalTask extends DefaultTask {

    // 声明输入:文本内容(会影响输出结果)
    @Input
    abstract Property<String> getInputText()

    // 声明输出:生成文件的目录(任务结果存放位置)
    @OutputDirectory
    abstract DirectoryProperty getOutputDir()

    @TaskAction
    void execute() {
        // 任务逻辑:将输入文本写入输出目录的文件
        def outputFile = outputDir.file("result.txt").get().asFile
        outputFile.parentFile.mkdirs()
        outputFile.text = inputText.get()
        println("执行任务:生成文件 ${outputFile.absolutePath}")
    }
}
2. 注册任务并配置

build.gradle 中注册任务并设置输入输出:

groovy 复制代码
tasks.register('myTask', MyIncrementalTask) {
    inputText.set("Hello, Incremental Build!") // 设置输入文本
    outputDir.set(file("$buildDir/myOutput"))   // 设置输出目录
}
3. 验证增量效果
  • 首次执行 ./gradlew myTask:任务执行(输出"执行任务...")。
  • 再次执行 ./gradlew myTask:输入未变,输出存在,任务标记为 UP-TO-DATE(跳过执行)。
  • 修改输入(如 inputText.set("新内容"))或删除输出目录:任务重新执行。

三、进阶:复杂输入输出声明

根据任务逻辑,需使用不同注解声明输入输出:

注解 用途 示例场景
@Input 简单类型输入(字符串、数字、布尔等) 配置参数、版本号
@InputFile 单个输入文件 依赖的源文件
@InputDirectory 输入目录(包含多个文件) 源文件目录
@InputFiles 多个输入文件/目录(文件集合) 分散的源文件集合
@OutputFile 单个输出文件 生成的单个目标文件
@OutputDirectory 输出目录(包含多个文件) 生成的目标文件目录
@Nested 嵌套对象(对象内的字段需声明输入) 复杂配置对象
示例:依赖文件的增量任务

若任务依赖某个文件的内容,需用 @InputFile 声明:

groovy 复制代码
abstract class FileProcessingTask extends DefaultTask {
    // 输入文件(内容变化会触发任务重新执行)
    @InputFile
    abstract RegularFileProperty getSourceFile()

    // 输出文件
    @OutputFile
    abstract RegularFileProperty getDestFile()

    @TaskAction
    void process() {
        def content = sourceFile.get().asFile.text
        destFile.get().asFile.text = content.toUpperCase() // 转换为大写
    }
}

// 注册任务
tasks.register('processFile', FileProcessingTask) {
    sourceFile.set(file("src/data.txt"))
    destFile.set(file("$buildDir/processed.txt"))
}

四、启用构建缓存(跨构建复用)

通过构建缓存,可复用其他机器或之前构建的任务结果(无需重新执行)。

1. 全局启用构建缓存

settings.gradle 中配置:

groovy 复制代码
buildCache {
    local {
        enabled = true // 启用本地缓存
        directory = file("$rootDir/.gradle/build-cache") // 缓存目录
    }
    // 可选:启用远程缓存(如S3、HTTP服务器)
    // remote(HttpBuildCache) {
    //     url = uri("http://example.com/cache")
    //     enabled = true
    // }
}
2. 允许任务参与缓存

在自定义 Task 中添加 @CacheableTask 注解:

groovy 复制代码
import org.gradle.api.tasks.CacheableTask

@CacheableTask // 标记任务可缓存
abstract class MyIncrementalTask extends DefaultTask {
    // ... 输入输出声明同上 ...
}
3. 验证缓存效果
  • 首次执行任务:生成结果并写入缓存。
  • 清除输出目录(rm -rf build)后再次执行:Gradle 从缓存中恢复结果(输出 FROM-CACHE)。

五、注意事项

  1. 输入输出必须完整声明

    • 遗漏输入:Gradle 无法感知变化,可能导致旧结果被复用(错误)。
    • 遗漏输出:Gradle 可能误判任务未生成结果,重复执行。
  2. 避免动态输入

    • 输入应是可序列化的、可比较的(如避免使用 new Date() 作为输入,可改为固定版本号)。
  3. 目录输出的特殊性

    • @OutputDirectory 会追踪目录内所有文件的变化,删除文件也会触发任务重新执行。
  4. 嵌套对象处理

    • 若输入是自定义对象,需用 @Nested 注解,并在对象内部为字段声明 @Input 等注解。

通过以上方式,自定义 Task 可实现"只构建变化内容",并利用构建缓存进一步提升效率。核心是让 Gradle 清晰掌握任务的输入输出依赖关系。

相关推荐
报错小能手6 小时前
项目——基于C/S架构的预约系统平台(3)
linux·开发语言·笔记·学习·架构·1024程序员节
cxr8286 小时前
涌现的架构:集体智能框架构建解析
人工智能·语言模型·架构·1024程序员节·ai智能体·ai赋能
CaracalTiger6 小时前
告别云端依赖!ComfyUI本地化视频生成实战教程+cpolar实战
python·gpt·开源·aigc·ai编程·1024程序员节·ai-native
心寒丶6 小时前
Linux基础知识(三、Linux常见操作目录命令)
linux·运维·服务器·1024程序员节
W.Y.B.G6 小时前
css3 学习笔记
笔记·学习·css3·1024程序员节
spencer_tseng6 小时前
JDK 9 List.of(...)
java·windows·list·1024程序员节
大明者省6 小时前
多类别分类中,标签的 “独热编码” 形式与输出层神经元的位置处理过程
1024程序员节
Bruce_Liuxiaowei6 小时前
[特殊字符] 排查VMnet1无IP的步骤
网络·网络协议·1024程序员节
今天又在学代码写BUG口牙7 小时前
MFC应用程序,工作线程学习记录
c++·mfc·1024程序员节