Use @Incremental with files being both input and output?

I have a custom task fixing whitespace issues in source files (code). The source files is both input and output to the task. I do not see how to combine the incremental input changes API together with input files also being the output files. Is there a way?

Currently the task works and is defined like this:

open class FixWhitespace : DefaultTask() {
  
  @OutputFiles
  lateinit var sourceFiles: FileTree

  @TaskAction
  fun fix() = ...
}

Only, when Gradle detects a change in one of the sourceFiles the task is re-run and doing its whitespace-thing. Great! The BIG drawback is however that the task is applied to all sourceFiles even if only one source file is modified.

I would like to take advantage of the incremental changes functionality in Gradle, only needing to apply the whitespace fixing to the source files actually changed, using something like this:

open class FixWhitespace : DefaultTask() {

  @Incremental
  @OutputFiles
  lateinit var sourceFiles: FileTree

  @TaskAction
  fun fix(changes: InputChanges) = changes.getFileChanges(sourceFiles).forEach { ... }
}

However, this seems to be hard to get working. Combining the annotation @Incremental and @OutputFiles throws exception in method InputChanges.getFileChanges:

> Cannot query incremental changes: No property found for value file tree. Incremental properties:

Instead, combining the annotation @Incremental and @InputFiles in sourceFiles, seems to have strange effects in my case. It deletes all sourceFiles before running the task, if the current build run happens to be non-incremental. As specified in API, when a full rebuild happens “output files of the work are removed prior to executing the work action”. Further, the task needs some specified outputs in order to ever get UP-TO-DATE, right?

So, is there a way to use InputChanges together with a FileTree being both the input and the output to the task?

FYI: I’m running Gradle version 5.6.4

Hi Joakim,

this is currently not possible. What you can do have your task create the fixed source files in a new location and then copy them back in a separate task. I know that this is far from idea :frowning:

Cheers,
Stefan

Hi Stefan, thanks for the quick and clear answer.

My workaround seems to be working: Single task updates a file if it’s modified.

import my.FileUpdater
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.*

@CacheableTask
abstract class UpdateFile : DefaultTask() {
    @get:OutputFile // Output is required at minimum
    abstract val file: RegularFileProperty

    @get:Input
    abstract val modified: Property<Long>

    private lateinit var updater: FileUpdater

    protected fun init(updater: FileUpdater) {
        this.updater = updater
        modified.set(updater.lastModified)
        file.set(updater.file)
    }

    @TaskAction
    fun update() {
        didWork = updater.update()
    }
}