Possible issue with Java Plugin

I’m working on a incremental task plugin that needs to inject code in class files. Because of it it monitors changes in the targetDirectory.

For the first run everything goes well and classes are injected with necessary modifications. Although, when executing for the second time, the Java Plugin compileTestJava task will detect those changes and rebuild the classes once again and with it removing the introduced modifications.

This does not seem to be the appropriate behavior for such task as it should only monitor for changes in the source directories. The question is… why is the compileTestJava task monitoring the targetDirectory and, how can I avoid it from doing so?

./gradlew build -i Starting Build Settings evaluated using empty settings script. Projects loaded. Root project using build file ‘/Users/thename/git/cola-gradle-plugin/consumer/build.gradle’. Included projects: [root project ‘consumer’] Evaluating root project ‘consumer’ using build file ‘/Users/thename/git/cola-gradle-plugin/consumer/build.gradle’. Incremental java compilation is an incubating feature. All projects evaluated. Selected primary task ‘build’ from project : Tasks to be executed: [task ‘:compileJava’, task ‘:processResources’, task ‘:classes’, task ‘:jar’, task ‘:assemble’, task ‘:compileTestJava’, task ‘:processTestResources’, task ‘:testClasses’, task ‘:icolac’, task ‘:test’, task ‘:check’, task ‘:build’] :compileJava (Thread[main,5,main]) started. :compileJava Skipping task ‘:compileJava’ as it is up-to-date (took 0.042 secs). :compileJava UP-TO-DATE :compileJava (Thread[main,5,main]) completed. Took 0.07 secs. :processResources (Thread[main,5,main]) started. :processResources Skipping task ‘:processResources’ as it has no source files. :processResources UP-TO-DATE :processResources (Thread[main,5,main]) completed. Took 0.003 secs. :classes (Thread[main,5,main]) started. :classes Skipping task ‘:classes’ as it has no actions. :classes UP-TO-DATE :classes (Thread[main,5,main]) completed. Took 0.001 secs. :jar (Thread[main,5,main]) started. :jar Skipping task ‘:jar’ as it is up-to-date (took 0.013 secs). :jar UP-TO-DATE :jar (Thread[main,5,main]) completed. Took 0.017 secs. :assemble (Thread[main,5,main]) started. :assemble Skipping task ‘:assemble’ as it has no actions. :assemble UP-TO-DATE :assemble (Thread[main,5,main]) completed. Took 0.002 secs. :compileTestJava (Thread[main,5,main]) started. :compileTestJava Executing task ‘:compileTestJava’ (up-to-date check took 0.117 secs) due to:

Output file /Users/thename/git/cola-gradle-plugin/consumer/build/classes/test/org/gradle/cola/tests/TestBase.class has changed.

Output file /Users/thename/git/cola-gradle-plugin/consumer/build/classes/test/org/gradle/cola/tests/PersonTest.class has changed. All input files are considered out-of-date for incremental task ‘:compileTestJava’. Compiling with JDK Java compiler API. :compileTestJava (Thread[main,5,main]) completed. Took 0.517 secs. :processTestResources (Thread[main,5,main]) started. :processTestResources Skipping task ‘:processTestResources’ as it is up-to-date (took 0.006 secs). :processTestResources UP-TO-DATE :processTestResources (Thread[main,5,main]) completed. Took 0.011 secs. :testClasses (Thread[main,5,main]) started. :testClasses Skipping task ‘:testClasses’ as it has no actions. :testClasses (Thread[main,5,main]) completed. Took 0.001 secs. :icolac (Thread[main,5,main]) started. :icolac …

Gradle evaluates both the inputs and outputs of a task. The idea is that you want to know if anything has changed what you last produced, because if so, any downstream tasks can’t reasonably trust that what they are consuming is correct (i.e. you would have to re-run the task to ensure the outputs are correct). When you have a task that is modifying the outputs of another task, they should be producing their own outputs rather than changing the outputs of the original task. So if they are consuming files from one directory, then they should write their outputs to a different directory. If you’re using a tool that changes files in place, then you probably want your task to first copy the files and then modify them.

Gary,

Please bear with me as I’m pretty new to gradle.

Here’s what I’m trying to do:

… compileTestJava -> compiles java classes in build/classes/test … icolac (my incremental task) -> inject test methods in compiled java classes in build/classes/test … test -> execute injected methods of test classes …

The task icolac needs to have as input and output the build/classes/test directory since it’s listening to updates to class files. This does not work but, from what you are telling me, I should be able to set the output of icolac task to a different directory (say build/classes/icolac) and then use it as the input for the java plugin test task.

Can I change the test task input directory? Or is there another alternative?

You can definitely change the input directory for the test task, and I would argue that that’s the right way to do it. Essentially, all you are trying to do is add another transform task in between compile and test. I would just add a property on your icolac task that exposes the directory that you write your classes to and then point the test task to that.

test {
  testClassesDir = icolac.destinationDir
}

Gary,

Thanks for your answer. Based on your input I was able to create an incremental task but one issue that arises with it is that changing the “test” task “testClassesDir” does not update the test classpath, meaning that the tests will not be executed from the correct directory.

I would think that, if one can modify the testClassDir that the test classpath should also be automatically updated.

As a workaround, I’ve created another task that is executed just before the test task.

Question: is there an alternative to this?

You can see the details here: https://github.com/bmsantos/cola-gradle-plugin

Any inputs or improvements would be welcomed since I’m really trying to provide a good user experience with it.