Use the hash of task classes to invalidate incremental-build caches

Currently, when I am updating our build logic, I often come into the problem when incremental tasks would not run because the input timestamps are older than the outputs. The workaround is to use --rerun-tasks, but even if I mention it in an email to my colleagues, there are always people that do not pay attention and spend hours on chasin ghosts.

It would be nice if Gradle would detect the change in the build script or in task implementation and purge the caches related to that task or even all caches.

In addition, I noticed that if I manually edit an output of a task, the output is still considered up to date based on timestamp. That may be desirable in certain cases, but often I would rather regenerate. Ideally that would be an option of the task, defaulting to regeneration on hash mismatch (the more conservative option).

Hi,

The change tracking is actually based on content (i.e. hashes), not timestamps. It sounds like something strange is going on with your setup.

Would you mind putting an example project up on GitHub (or similar) with step by step instructions on how to replicate the behaviour you’re seeing?

Apologies, turns out the content hashing does work - just some of our generation tasks disabled the incremental stuff because the authors found that Gradle spends too much time scanning the output dir, even if we already knew it needs to be regenerated.

This still leaves open the problem with changing task implementation - consider this build:

ext.model = file("src/model/props/messages.properties")
ext.generationRoot = file("src/main/generated")
  task setup << {
    generationRoot.mkdirs()
    model.parentFile.mkdirs()
    model.text = "foo=barx\nbaz=qux\n"
}
  task generate {
    inputs.source model
    outputs.files generationRoot
      doLast {
        def props = new Properties()
        model.withReader { props.load it }
        props.each { k, v ->
            new File(generationRoot, "${k}.java").text = """
            class ${k.toString()} {
                public void $v() { System.out.println("${new Date()}"); }
            }
            """
        }
    }
}
  1. Copy to an empty directory and run the “setup” task to create the layout. 2. Run the “generate” task to generate some java files in “src/main/generated”. 3. Change the template, for example instead of printing the “new Date()” to print “System.currentTimeMillis” 4. Run the “generate” task to regenerate - nothing happens as the inputs are up to date

What I was getting at in the original request is that Gradle can intercept the classloading, or scan the classpath and add automatically add the binary content of the incremental task classes to the inputs of an incremental task.

This situation happens more than a few times a week as we maintain our code generator and is particularly tricky when you pick somebody else’s changes from version control.

Right, not tracking implementation classpath is a known shortcoming and something we are working towards (see this spec story: https://github.com/gradle/gradle/blob/master/design-docs/incremental-build.md#story-invalidate-task-outputs-when-task-implementation-changes).

The workaround at the moment is to promote inputs explicitly.

So your task would look like this:

task generate {
    inputs.source model
    outputs.files generationRoot
      def content = """
            class ${k.toString()} {
                public void $v() { System.out.println("${new Date()}"); }
            }
            """
      inputs.property "content", content
      doLast {
        def props = new Properties()
        model.withReader { props.load it }
        props.each { k, v ->
            new File(generationRoot, "${k}.java").text = content
        }
    }
}