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 清晰掌握任务的输入输出依赖关系。

相关推荐
fangzhanpeng1682 天前
快速掌握C#语言基础知识点(1.基础数据类型)
开发语言·c#·1024程序员节
无己心4 天前
面向多平台 AI 搜索引擎的适配层架构设计
前端·人工智能·搜索引擎·ai·1024程序员节
u01030552710 天前
AI驱动的安卓UI自动生成功能实现
人工智能·1024程序员节
u01030552711 天前
长株潭智能体赋能腾讯元器链接共享
1024程序员节
云贝贝贝12 天前
OceanBase认证体系介绍与备考指南
运维·服务器·oceanbase·1024程序员节
u01030552712 天前
Java图像处理实战指南
人工智能·1024程序员节
u01030552713 天前
Java AWT鼠标事件全解析
人工智能·1024程序员节
u01030552715 天前
Java实用类应用精讲
人工智能·1024程序员节
u01030552715 天前
Java I/O核心操作实战
人工智能·1024程序员节
u01030552716 天前
ArrayList操作详解与实战应用
人工智能·1024程序员节