For any new Spring Boot project gradle's task bootBuildInfo won't work with gradle's configuration cache.
Spring Boot 2.6.6
How to reproduce issue, clone project: https://github.com/wyhasany/reproduce-spring-boot-gradle-bootbuildinfo-config-cache-incompatibility
git clone https://github.com/wyhasany/reproduce-spring-boot-gradle-bootbuildinfo-config-cache-incompatibility.git
cd reproduce-spring-boot-gradle-bootbuildinfo-config-cache-incompatibility
./gradlew bootBuildInfo --console=plain -i #first run works correctly
./gradlew bootBuildInfo --console=plain -i #second run fails
Second run fails with following error:
* What went wrong:
Execution failed for task ':bootBuildInfo'.
> Cannot invoke "org.gradle.api.file.DirectoryProperty.getAsFile()" because "this.destinationDir" is null
* Try:
> Run with --debug option to get more log output.
> Run with --scan to get full insights.
* Exception is:
org.gradle.api.tasks.TaskExecutionException: Execution failed for task ':bootBuildInfo'.
at org.gradle.api.internal.tasks.properties.DefaultTaskProperties.resolve(DefaultTaskProperties.java:76)
at org.gradle.execution.plan.LocalTaskNode.resolveMutations(LocalTaskNode.java:231)
at org.gradle.execution.plan.DefaultExecutionPlan.getResolvedMutationInfo(DefaultExecutionPlan.java:737)
at org.gradle.execution.plan.DefaultExecutionPlan.selectNext(DefaultExecutionPlan.java:649)
at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.lambda$executeNextNode$2(DefaultPlanExecutor.java:197)
at org.gradle.internal.resources.DefaultResourceLockCoordinationService.withStateLock(DefaultResourceLockCoordinationService.java:45)
at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.executeNextNode(DefaultPlanExecutor.java:186)
at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.run(DefaultPlanExecutor.java:140)
at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64)
at org.gradle.internal.concurrent.ManagedExecutorImpl$1.run(ManagedExecutorImpl.java:48)
Caused by: java.lang.NullPointerException: Cannot invoke "org.gradle.api.file.DirectoryProperty.getAsFile()" because "this.destinationDir" is null
at org.springframework.boot.gradle.tasks.buildinfo.BuildInfo.getDestinationDir(BuildInfo.java:80)
at org.springframework.boot.gradle.tasks.buildinfo.BuildInfo_Decorated.getDestinationDir(Unknown Source)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at org.gradle.api.internal.tasks.properties.bean.AbstractNestedRuntimeBeanNode$BeanPropertyValue$1$1.create(AbstractNestedRuntimeBeanNode.java:77)
at org.gradle.internal.deprecation.DeprecationLogger.whileDisabled(DeprecationLogger.java:244)
at org.gradle.api.internal.tasks.properties.bean.AbstractNestedRuntimeBeanNode$BeanPropertyValue$1.get(AbstractNestedRuntimeBeanNode.java:73)
at com.google.common.base.Suppliers$NonSerializableMemoizingSupplier.get(Suppliers.java:167)
at org.gradle.api.internal.tasks.properties.bean.AbstractNestedRuntimeBeanNode$BeanPropertyValue.getUnprocessedValue(AbstractNestedRuntimeBeanNode.java:139)
at org.gradle.api.internal.tasks.properties.FileParameterUtils.resolveOutputFilePropertySpecs(FileParameterUtils.java:116)
at org.gradle.api.internal.tasks.properties.OutputUnpacker.visitOutputFileProperty(OutputUnpacker.java:67)
at org.gradle.api.internal.tasks.properties.CompositePropertyVisitor.visitOutputFileProperty(CompositePropertyVisitor.java:69)
at org.gradle.api.internal.tasks.properties.annotations.AbstractOutputPropertyAnnotationHandler.visitPropertyValue(AbstractOutputPropertyAnnotationHandler.java:50)
at org.gradle.api.internal.tasks.properties.bean.AbstractNestedRuntimeBeanNode.visitProperties(AbstractNestedRuntimeBeanNode.java:56)
at org.gradle.api.internal.tasks.properties.bean.RootRuntimeBeanNode.visitNode(RootRuntimeBeanNode.java:32)
at org.gradle.api.internal.tasks.properties.DefaultPropertyWalker.visitProperties(DefaultPropertyWalker.java:41)
at org.gradle.api.internal.tasks.TaskPropertyUtils.visitProperties(TaskPropertyUtils.java:42)
at org.gradle.api.internal.tasks.properties.DefaultTaskProperties.resolve(DefaultTaskProperties.java:67)
at org.gradle.execution.plan.LocalTaskNode.resolveMutations(LocalTaskNode.java:231)
at org.gradle.execution.plan.DefaultExecutionPlan.getResolvedMutationInfo(DefaultExecutionPlan.java:737)
at org.gradle.execution.plan.DefaultExecutionPlan.selectNext(DefaultExecutionPlan.java:649)
at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.lambda$executeNextNode$2(DefaultPlanExecutor.java:197)
at org.gradle.internal.resources.DefaultResourceLockCoordinationService.withStateLock(DefaultResourceLockCoordinationService.java:45)
at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.executeNextNode(DefaultPlanExecutor.java:186)
at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.run(DefaultPlanExecutor.java:140)
at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64)
at org.gradle.internal.concurrent.ManagedExecutorImpl$1.run(ManagedExecutorImpl.java:48)
It seems that for some reason constructor: https://github.com/spring-projects/spring-boot/blob/b0fb2128366a63e66abaf228ab5627e3ad0152b9/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/buildinfo/BuildInfo.java#L52-L55 is not executed once configuration cache is enabled.
I guess that happens as we use there explicit Project instead of this we probably should use layout:
https://docs.gradle.org/current/userguide/configuration_cache.html#config_cache:requirements:use_project_during_execution
I think bootBuildInfo should be documented as a configuration-cache incompatible task or this bug should be fixed somehow.
Comment From: wilkinsona
Thanks for the report. Using the project to create a property that captures the build directory is exactly what the documentation recommends so BuildInfo should already be compatible with the configuration cache.
If you replace the use of the springBoot DSL with manual configuration of the task, it works:
def bootBuildInfo = tasks.register("bootBuildInfo", org.springframework.boot.gradle.tasks.buildinfo.BuildInfo) {
properties {
time = EPOCH
}
}
tasks.named("classes") {
dependsOn bootBuildInfo
}
$ gradle bootBuildInfo --console=plain
Configuration cache is an incubating feature.
Calculating task graph as configuration cache cannot be reused because file 'build.gradle' has changed.
> Task :bootBuildInfo
BUILD SUCCESSFUL in 594ms
1 actionable task: 1 executed
Configuration cache entry stored.
$ gradle bootBuildInfo --console=plain
Configuration cache is an incubating feature.
Reusing configuration cache.
> Task :bootBuildInfo UP-TO-DATE
BUILD SUCCESSFUL in 719ms
1 actionable task: 1 up-to-date
Configuration cache entry reused.
I can't yet explain why the task configured via the DSL does not work, but I suspect it may be a bug in Gradle. I'll ask the Gradle team to take a look.
Comment From: wyhasany
Thanks for that workaround, it works at my computer as well. I'm curious what Gradle team would say.
Comment From: wyhasany
What I've discovered with your approach build-info.properties is no added into demo.jar after executing:
./gradlew clean build --console=plain
after using previous version of build.gradle it works correctly but only without configuration cache.
Comment From: wilkinsona
I neglected to configure the destination directory so that the file's written to a location that's packaged in the jar by default. This should work:
def bootBuildInfo = tasks.register("bootBuildInfo", org.springframework.boot.gradle.tasks.buildinfo.BuildInfo) {
properties {
time = EPOCH
}
destinationDir = sourceSets.main.output.resourcesDir
}
tasks.named("classes") {
dependsOn bootBuildInfo
}
Comment From: wilkinsona
@wyhasany The Gradle team have confirmed that this appears to be a Gradle bug. Can you please create a Gradle bug report so that they can investigate? In the meantime, we'll investigate a workaround here that allows you to continue to use the DSL rather than registering the task yourself.
Comment From: wyhasany
@wilkinsona ofc I'm going to make a bug for Gradle team. However I struggled a while to place build-info.properties into a proper place (under META-INF directory) working configuration looks as follows:
def bootBuildInfo = tasks.register("bootBuildInfo", BuildInfo) {
properties {
time = java.time.Instant.EPOCH
}
destinationDir = new File(sourceSets.main.output.resourcesDir.toString(), "META-INF")
}
classes.dependsOn bootBuildInfo
Comment From: KENNYSOFT
Thanks for the workaround. For someone who looking for groovy syntax:
task bootBuildInfo(type: org.springframework.boot.gradle.tasks.buildinfo.BuildInfo) {
properties {
time = java.time.Instant.EPOCH
}
destinationDir = new File(sourceSets.main.output.resourcesDir.toString(), 'META-INF')
}
classes.dependsOn bootBuildInfo
Comment From: Emad-Aghayi-kr
I add the code you provided. But it throws this error:
Caused by: org.gradle.api.internal.tasks.DefaultTaskContainer$DuplicateTaskException: Cannot add task 'bootBuildInfo' as a task with that name already exists.
@KENNYSOFT do you have any idea what should I do next?
Comment From: KENNYSOFT
@emadaghayi Did you remove buildInfo() from springBoot block?
Comment From: wilkinsona
Closing in favor of https://github.com/gradle/gradle/issues/20469.