Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Due to gradle/gradle#5510, compileJava may be executed unnecessarily when not using Gradle's daemon #14054

Closed
display-none opened this issue Aug 14, 2018 · 5 comments
Labels
type: bug A general bug
Milestone

Comments

@display-none
Copy link

display-none commented Aug 14, 2018

spring-boot-gradle-plugin applied to a project build with no-daemon Gradle breaks incremental build - causes a retriggering of compileJava with every run

Steps to reproduce:

Expected behaviour
Task compileJava is UP-TO-DATE as nothing has changed that should trigger recompilation.

Actual behaviour
Task compileJava is run because, as Gradle prints, "Task ':compileJava' has additional actions that have changed"

Probable cause
During up-to-date checks Gradle compares task's previously known actions with current actions for equality (see org.gradle.api.internal.changedetection.rules.TaskTypeTaskStateChanges). Spring boot's org.springframework.boot.gradle.plugin.JavaPluginAction configures a JavaCompile task in configureAdditionalMetadataLocations with an action using a labda.

When building with a daemon that configuration probably happens once and in JavaCompile task's actions we can see the same lambda instance, which makes it equal and up-to-date check passes. When executing without a daemon another run has a different lambda instance and it fails the equality check.

In our project we have multiple steps configured as separate gradle runs and each of them recompiles the whole project.

Versions
Gradle 4.9
Spring Boot 2.0.4.RELEASE

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged label Aug 14, 2018
@wilkinsona
Copy link
Member

wilkinsona commented Aug 14, 2018

Thanks for the report. I don't think this is quite as simple has you have described. In my testing, compileJava is sometimes up-to-date and whether or not it is considered to be up-to-date seems to change depending on whether or not --info is passed in.

Initial clean run, two tasks are executed as expected:

$ ./gradlew --no-daemon clean compileJava

BUILD SUCCESSFUL in 4s
2 actionable tasks: 2 executed

Run compileJava again and it is up-to-date:

$ ./gradlew --no-daemon compileJava

BUILD SUCCESSFUL in 3s
1 actionable task: 1 up-to-date

Run compileJava again but with --info and it is no longer up-to-date:

$ ./gradlew --no-daemon compileJava --info
Initialized native services in: /Users/awilkinson/.gradle/native
Using 8 worker leases.
Starting Build
Settings evaluated using settings file '/Users/awilkinson/dev/spring-guides/gs-spring-boot/complete/settings.gradle'.
Projects loaded. Root project using build file '/Users/awilkinson/dev/spring-guides/gs-spring-boot/complete/build.gradle'.
Included projects: [root project 'complete']

> Configure project :
Evaluating root project 'complete' using build file '/Users/awilkinson/dev/spring-guides/gs-spring-boot/complete/build.gradle'.
Applying dependency management to configuration 'annotationProcessor' in project 'complete'
Applying dependency management to configuration 'apiElements' in project 'complete'
Applying dependency management to configuration 'archives' in project 'complete'
Applying dependency management to configuration 'bootArchives' in project 'complete'
Applying dependency management to configuration 'compile' in project 'complete'
Applying dependency management to configuration 'compileClasspath' in project 'complete'
Applying dependency management to configuration 'compileOnly' in project 'complete'
Applying dependency management to configuration 'default' in project 'complete'
Applying dependency management to configuration 'implementation' in project 'complete'
Applying dependency management to configuration 'runtime' in project 'complete'
Applying dependency management to configuration 'runtimeClasspath' in project 'complete'
Applying dependency management to configuration 'runtimeElements' in project 'complete'
Applying dependency management to configuration 'runtimeOnly' in project 'complete'
Applying dependency management to configuration 'testAnnotationProcessor' in project 'complete'
Applying dependency management to configuration 'testCompile' in project 'complete'
Applying dependency management to configuration 'testCompileClasspath' in project 'complete'
Applying dependency management to configuration 'testCompileOnly' in project 'complete'
Applying dependency management to configuration 'testImplementation' in project 'complete'
Applying dependency management to configuration 'testRuntime' in project 'complete'
Applying dependency management to configuration 'testRuntimeClasspath' in project 'complete'
Applying dependency management to configuration 'testRuntimeOnly' in project 'complete'
All projects evaluated.
Selected primary task 'compileJava' from project :
Tasks to be executed: [task ':compileJava']
:compileJava (Thread[main,5,main]) started.

> Task :compileJava
Resolving global dependency management for project 'complete'
Excluding [org.apache.tomcat:tomcat-annotations-api]
Excluding []
Task ':compileJava' is not up-to-date because:
  Task ':compileJava' has additional actions that have changed
All input files are considered out-of-date for incremental task ':compileJava'.
Compiling with JDK Java compiler API.
:compileJava (Thread[main,5,main]) completed. Took 1.709 secs.

BUILD SUCCESSFUL in 4s
1 actionable task: 1 executed

Repeat the run with --info and compileJava is up-to-date again:

./gradlew --no-daemon compileJava --info
Initialized native services in: /Users/awilkinson/.gradle/native
Using 8 worker leases.
Starting Build
Settings evaluated using settings file '/Users/awilkinson/dev/spring-guides/gs-spring-boot/complete/settings.gradle'.
Projects loaded. Root project using build file '/Users/awilkinson/dev/spring-guides/gs-spring-boot/complete/build.gradle'.
Included projects: [root project 'complete']

> Configure project :
Evaluating root project 'complete' using build file '/Users/awilkinson/dev/spring-guides/gs-spring-boot/complete/build.gradle'.
Applying dependency management to configuration 'annotationProcessor' in project 'complete'
Applying dependency management to configuration 'apiElements' in project 'complete'
Applying dependency management to configuration 'archives' in project 'complete'
Applying dependency management to configuration 'bootArchives' in project 'complete'
Applying dependency management to configuration 'compile' in project 'complete'
Applying dependency management to configuration 'compileClasspath' in project 'complete'
Applying dependency management to configuration 'compileOnly' in project 'complete'
Applying dependency management to configuration 'default' in project 'complete'
Applying dependency management to configuration 'implementation' in project 'complete'
Applying dependency management to configuration 'runtime' in project 'complete'
Applying dependency management to configuration 'runtimeClasspath' in project 'complete'
Applying dependency management to configuration 'runtimeElements' in project 'complete'
Applying dependency management to configuration 'runtimeOnly' in project 'complete'
Applying dependency management to configuration 'testAnnotationProcessor' in project 'complete'
Applying dependency management to configuration 'testCompile' in project 'complete'
Applying dependency management to configuration 'testCompileClasspath' in project 'complete'
Applying dependency management to configuration 'testCompileOnly' in project 'complete'
Applying dependency management to configuration 'testImplementation' in project 'complete'
Applying dependency management to configuration 'testRuntime' in project 'complete'
Applying dependency management to configuration 'testRuntimeClasspath' in project 'complete'
Applying dependency management to configuration 'testRuntimeOnly' in project 'complete'
All projects evaluated.
Selected primary task 'compileJava' from project :
Tasks to be executed: [task ':compileJava']
:compileJava (Thread[Task worker for ':' Thread 2,5,main]) started.

> Task :compileJava UP-TO-DATE
Resolving global dependency management for project 'complete'
Excluding [org.apache.tomcat:tomcat-annotations-api]
Excluding []
Skipping task ':compileJava' as it is up-to-date.
:compileJava (Thread[Task worker for ':' Thread 2,5,main]) completed. Took 1.064 secs.

BUILD SUCCESSFUL in 3s
1 actionable task: 1 up-to-date

When Gradle's comparing task actions it uses the class loader for the action and the type name. It's JavaPluginAction$$Lambda$22 that we're interested in.

Here is the name (as captured from the output of -verbose:class) when built without --info:

[Loaded org.springframework.boot.gradle.plugin.JavaPluginAction$$Lambda$22/165631567 from org.springframework.boot.gradle.plugin.JavaPluginAction]

And here's the name with --info:

[Loaded org.springframework.boot.gradle.plugin.JavaPluginAction$$Lambda$22/639343159 from org.springframework.boot.gradle.plugin.JavaPluginAction]

And the names from another run with --info:

[Loaded org.springframework.boot.gradle.plugin.JavaPluginAction$$Lambda$22/639343159 from org.springframework.boot.gradle.plugin.JavaPluginAction]

In this last build compileJava was up-to-date as the type name of the action hasn't changed.

If --info is removed, the name goes back to what it was in the first run:

[Loaded org.springframework.boot.gradle.plugin.JavaPluginAction$$Lambda$22/165631567 from org.springframework.boot.gradle.plugin.JavaPluginAction]

For reasons that I do not yet understand, the type names appear to change depending on the command line that's used to launch Gradle, and appears to do so predictably.

We could work around this behaviour in the plugin by using anonymous inner classes rather than lambdas for task actions, but it feels like it would be better if that was addressed in Gradle so that all plugin authors can safely use lambdas for task actions.

@wilkinsona
Copy link
Member

This is a known problem: gradle/gradle#5510. I'm going to see if I can create a minimal example that reproduces the problem.

@display-none
Copy link
Author

You're right, for some reason in my test runs I always had a recompilation, but now I ran
gradlew --no-daemon compileJava --console=plain
and got 2 up-to-dates in 5 runs. No --info was needed. So it looks like it's not directly dependent on that.

Originally I used the following to see that the lambda changes:

gradle.buildFinished {
	tasks['compileJava'].actions.forEach {
		logger.info(it.getDisplayName())
		logger.info(it.getActionClassName())
	}
}

@wilkinsona
Copy link
Member

Irrespective of a change in Gradle, we should probably do something about this in our plugin. If we don't, users on current versions of Gradle will still be affected by the problem.

@wilkinsona wilkinsona changed the title gradle plugin breaks incremental build Due to gradle/gradle#5510, compileJava may be executed unnecessarily when not using Gradle's daemon Aug 14, 2018
@wilkinsona wilkinsona added type: bug A general bug and removed status: waiting-for-triage An issue we've not yet triaged labels Aug 14, 2018
@wilkinsona wilkinsona added this to the 2.0.x milestone Aug 14, 2018
@david-noel
Copy link
Contributor

Thanks for swapping out the lambdas. I'm looking forward to this being released.

@wilkinsona wilkinsona modified the milestones: 2.0.x, 2.0.5 Aug 14, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type: bug A general bug
Projects
None yet
Development

No branches or pull requests

4 participants