Skip to content

增量构建

增量构建是 Gradle 最重要的性能特性之一:当任务的输入和输出没有变化时,自动跳过执行,显示 UP-TO-DATE

工作原理

任务执行前检查:
  1. 任务是否有 @Input/@Output 注解?
  2. 输入文件/属性的哈希值是否与上次一致?
  3. 输出文件是否存在且与上次一致?

如果三者都满足 → 跳过执行(UP-TO-DATE)
否则 → 重新执行

任务状态

bash
./gradlew build

# 任务状态说明:
# > Task :compileJava          ← 执行了(EXECUTED)
# > Task :test UP-TO-DATE      ← 跳过,没有变化
# > Task :jar SKIPPED          ← 被 -x 跳过或 enabled=false
# > Task :check FROM-CACHE     ← 从构建缓存恢复
# > Task :build NO-SOURCE      ← 没有源文件,跳过

让自定义任务支持增量构建

kotlin
abstract class ProcessFilesTask : DefaultTask() {
    @get:InputDirectory
    abstract val sourceDir: DirectoryProperty
    
    @get:Input
    abstract val encoding: Property<String>
    
    @get:OutputDirectory
    abstract val outputDir: DirectoryProperty
    
    @TaskAction
    fun process() {
        val src = sourceDir.get().asFile
        val out = outputDir.get().asFile
        val enc = encoding.get()
        
        src.walkTopDown()
            .filter { it.isFile && it.extension == "txt" }
            .forEach { file ->
                val outFile = File(out, file.relativeTo(src).path)
                outFile.parentFile.mkdirs()
                outFile.writeText(file.readText(charset(enc)).uppercase(), charset(enc))
            }
    }
}

强制重新执行

bash
# 强制重新执行(忽略增量构建)
./gradlew build --rerun-tasks

# 只重新执行某个任务
./gradlew :test --rerun

禁用增量构建

kotlin
tasks.named("myTask") {
    outputs.upToDateWhen { false }  // 每次都执行
}

增量任务动作(Incremental Task Action)

对于大量文件的处理,增量任务动作只处理变更的文件

kotlin
abstract class IncrementalProcessTask : DefaultTask() {
    @get:Incremental
    @get:InputDirectory
    abstract val inputDir: DirectoryProperty
    
    @get:OutputDirectory
    abstract val outputDir: DirectoryProperty
    
    @TaskAction
    fun process(inputChanges: InputChanges) {
        // 只处理变更的文件
        inputChanges.getFileChanges(inputDir).forEach { change ->
            val outFile = outputDir.file(change.normalizedPath).get().asFile
            when (change.changeType) {
                ChangeType.ADDED, ChangeType.MODIFIED -> {
                    outFile.parentFile.mkdirs()
                    outFile.writeText(change.file.readText().uppercase())
                    println("处理: ${change.file.name}")
                }
                ChangeType.REMOVED -> {
                    outFile.delete()
                    println("删除: ${change.file.name}")
                }
            }
        }
    }
}

下一步