diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/buildinfo/BuildInfo.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/buildinfo/BuildInfo.java index 4d8e88eed2..2246248790 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/buildinfo/BuildInfo.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/buildinfo/BuildInfo.java @@ -24,6 +24,7 @@ import java.util.Map; import org.gradle.api.Action; import org.gradle.api.Project; import org.gradle.api.Task; +import org.gradle.api.file.DirectoryProperty; import org.gradle.api.internal.ConventionTask; import org.gradle.api.tasks.Nested; import org.gradle.api.tasks.OutputDirectory; @@ -44,7 +45,12 @@ public class BuildInfo extends ConventionTask { private final BuildInfoProperties properties = new BuildInfoProperties(getProject()); - private File destinationDir; + private final DirectoryProperty destinationDir; + + public BuildInfo() { + this.destinationDir = getProject().getObjects().directoryProperty() + .convention(getProject().getLayout().getBuildDirectory()); + } /** * Generates the {@code build-info.properties} file in the configured @@ -73,7 +79,7 @@ public class BuildInfo extends ConventionTask { */ @OutputDirectory public File getDestinationDir() { - return (this.destinationDir != null) ? this.destinationDir : getProject().getBuildDir(); + return this.destinationDir.getAsFile().get(); } /** @@ -81,7 +87,7 @@ public class BuildInfo extends ConventionTask { * @param destinationDir the destination directory */ public void setDestinationDir(File destinationDir) { - this.destinationDir = destinationDir; + this.destinationDir.set(destinationDir); } /** diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/buildinfo/BuildInfoProperties.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/buildinfo/BuildInfoProperties.java index 94270c1212..44d588cede 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/buildinfo/BuildInfoProperties.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/buildinfo/BuildInfoProperties.java @@ -16,6 +16,8 @@ package org.springframework.boot.gradle.tasks.buildinfo; +import java.io.IOException; +import java.io.ObjectInputStream; import java.io.Serializable; import java.time.Instant; import java.util.HashMap; @@ -23,6 +25,7 @@ import java.util.Map; import org.gradle.api.Project; import org.gradle.api.provider.Property; +import org.gradle.api.provider.Provider; import org.gradle.api.tasks.Input; import org.gradle.api.tasks.Optional; @@ -35,6 +38,8 @@ import org.gradle.api.tasks.Optional; @SuppressWarnings("serial") public class BuildInfoProperties implements Serializable { + private transient Instant creationTime = Instant.now(); + private final Property group; private final Property artifact; @@ -43,22 +48,35 @@ public class BuildInfoProperties implements Serializable { private final Property name; - private final Property time; + private final Property time; + + private boolean timeConfigured = false; private Map additionalProperties = new HashMap<>(); BuildInfoProperties(Project project) { - this.time = project.getObjects().property(Instant.class); - this.time.set(Instant.now()); + this.time = project.getObjects().property(Long.class); this.group = project.getObjects().property(String.class); this.group.set(project.provider(() -> project.getGroup().toString())); this.artifact = project.getObjects().property(String.class); this.version = project.getObjects().property(String.class); - this.version.set(project.provider(() -> project.getVersion().toString())); + this.version.set(projectVersion(project)); this.name = project.getObjects().property(String.class); this.name.set(project.provider(project::getName)); } + private Provider projectVersion(Project project) { + try { + Provider externalVersionProperty = project.getProviders().gradleProperty("version") + .forUseAtConfigurationTime(); + externalVersionProperty.getOrNull(); + } + catch (NoSuchMethodError ex) { + // Gradle < 6.5 + } + return project.provider(() -> project.getVersion().toString()); + } + /** * Returns the value used for the {@code build.group} property. Defaults to the * {@link Project#getGroup() Project's group}. @@ -142,7 +160,14 @@ public class BuildInfoProperties implements Serializable { @Input @Optional public Instant getTime() { - return this.time.getOrNull(); + Long epochMillis = this.time.getOrNull(); + if (epochMillis != null) { + return Instant.ofEpochMilli(epochMillis); + } + if (this.timeConfigured) { + return null; + } + return this.creationTime; } /** @@ -150,7 +175,8 @@ public class BuildInfoProperties implements Serializable { * @param time the build time */ public void setTime(Instant time) { - this.time.set(time); + this.timeConfigured = true; + this.time.set((time != null) ? time.toEpochMilli() : null); } /** @@ -173,4 +199,9 @@ public class BuildInfoProperties implements Serializable { this.additionalProperties = additionalProperties; } + private void readObject(ObjectInputStream input) throws ClassNotFoundException, IOException { + input.defaultReadObject(); + this.creationTime = Instant.now(); + } + } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/buildinfo/BuildInfoIntegrationTests.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/buildinfo/BuildInfoIntegrationTests.java index e02be7d307..e25342e897 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/buildinfo/BuildInfoIntegrationTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/buildinfo/BuildInfoIntegrationTests.java @@ -19,9 +19,13 @@ package org.springframework.boot.gradle.tasks.buildinfo; import java.io.File; import java.io.FileReader; import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.time.Instant; import java.util.Properties; -import org.gradle.testkit.runner.BuildResult; import org.gradle.testkit.runner.InvalidRunnerConfigurationException; import org.gradle.testkit.runner.TaskOutcome; import org.gradle.testkit.runner.UnexpectedBuildFailure; @@ -38,7 +42,7 @@ import static org.assertj.core.api.Assertions.assertThat; * * @author Andy Wilkinson */ -@GradleCompatibility +@GradleCompatibility(configurationCache = true) class BuildInfoIntegrationTests { GradleBuild gradleBuild; @@ -69,7 +73,14 @@ class BuildInfoIntegrationTests { @TestTemplate void notUpToDateWhenExecutedTwiceAsTimeChanges() { assertThat(this.gradleBuild.build("buildInfo").task(":buildInfo").getOutcome()).isEqualTo(TaskOutcome.SUCCESS); + Properties first = buildInfoProperties(); + String firstBuildTime = first.getProperty("build.time"); + assertThat(firstBuildTime).isNotNull(); assertThat(this.gradleBuild.build("buildInfo").task(":buildInfo").getOutcome()).isEqualTo(TaskOutcome.SUCCESS); + Properties second = buildInfoProperties(); + String secondBuildTime = second.getProperty("build.time"); + assertThat(secondBuildTime).isNotNull(); + assertThat(Instant.parse(firstBuildTime).isBefore(Instant.parse(secondBuildTime))); } @TestTemplate @@ -81,11 +92,22 @@ class BuildInfoIntegrationTests { } @TestTemplate - void notUpToDateWhenExecutedTwiceWithFixedTimeAndChangedProjectVersion() { - assertThat(this.gradleBuild.build("buildInfo", "-PnullTime").task(":buildInfo").getOutcome()) - .isEqualTo(TaskOutcome.SUCCESS); - BuildResult result = this.gradleBuild.build("buildInfo", "-PnullTime", "-PprojectVersion=0.2.0"); - assertThat(result.task(":buildInfo").getOutcome()).isEqualTo(TaskOutcome.SUCCESS); + void notUpToDateWhenExecutedTwiceWithFixedTimeAndChangedProjectVersion() throws IOException { + assertThat(this.gradleBuild.scriptProperty("projectVersion", "0.1.0").build("buildInfo").task(":buildInfo") + .getOutcome()).isEqualTo(TaskOutcome.SUCCESS); + assertThat(this.gradleBuild.scriptProperty("projectVersion", "0.2.0").build("buildInfo").task(":buildInfo") + .getOutcome()).isEqualTo(TaskOutcome.SUCCESS); + } + + @TestTemplate + void notUpToDateWhenExecutedTwiceWithFixedTimeAndChangedGradlePropertiesProjectVersion() throws IOException { + Path gradleProperties = new File(this.gradleBuild.getProjectDir(), "gradle.properties").toPath(); + Files.write(gradleProperties, "version=0.1.0".getBytes(StandardCharsets.UTF_8), StandardOpenOption.CREATE, + StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING); + assertThat(this.gradleBuild.build("buildInfo").task(":buildInfo").getOutcome()).isEqualTo(TaskOutcome.SUCCESS); + Files.write(gradleProperties, "version=0.2.0".getBytes(StandardCharsets.UTF_8), StandardOpenOption.CREATE, + StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING); + assertThat(this.gradleBuild.build("buildInfo").task(":buildInfo").getOutcome()).isEqualTo(TaskOutcome.SUCCESS); } @TestTemplate diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/buildinfo/BuildInfoTests.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/buildinfo/BuildInfoTests.java index 861343c6f6..a6524134b0 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/buildinfo/BuildInfoTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/buildinfo/BuildInfoTests.java @@ -24,6 +24,8 @@ import java.time.format.DateTimeFormatter; import java.util.Properties; import org.gradle.api.Project; +import org.gradle.api.internal.project.ProjectInternal; +import org.gradle.initialization.GradlePropertiesController; import org.gradle.testfixtures.ProjectBuilder; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; @@ -125,7 +127,10 @@ class BuildInfoTests { private Project createProject(String projectName) { File projectDir = new File(this.temp, projectName); - return ProjectBuilder.builder().withProjectDir(projectDir).withName(projectName).build(); + Project project = ProjectBuilder.builder().withProjectDir(projectDir).withName(projectName).build(); + ((ProjectInternal) project).getServices().get(GradlePropertiesController.class) + .loadGradlePropertiesFrom(projectDir); + return project; } private BuildInfo createTask(Project project) { diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/testkit/GradleBuild.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/testkit/GradleBuild.java index 439e6d661d..ba8cfbd581 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/testkit/GradleBuild.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/testkit/GradleBuild.java @@ -176,7 +176,7 @@ public class GradleBuild { new File(this.projectDir, "repository")); GradleRunner gradleRunner = GradleRunner.create().withProjectDir(this.projectDir) .withPluginClasspath(pluginClasspath()); - if (this.dsl != Dsl.KOTLIN) { + if (this.dsl != Dsl.KOTLIN && !this.configurationCache) { // see https://github.com/gradle/gradle/issues/6862 gradleRunner.withDebug(true); } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/buildinfo/BuildInfoIntegrationTests-basicExecution.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/buildinfo/BuildInfoIntegrationTests-basicExecution.gradle new file mode 100644 index 0000000000..826d2e21e3 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/buildinfo/BuildInfoIntegrationTests-basicExecution.gradle @@ -0,0 +1,15 @@ +plugins { + id 'org.springframework.boot' version '{version}' apply false +} + +version = '0.1.0' + +task buildInfo(type: org.springframework.boot.gradle.tasks.buildinfo.BuildInfo) { + destinationDir project.buildDir + properties { + artifact = 'foo' + group = 'foo' + name = 'foo' + additional = ['additional': 'foo'] + } +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/buildinfo/BuildInfoIntegrationTests-defaultValues.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/buildinfo/BuildInfoIntegrationTests-defaultValues.gradle index 62b7abef2b..cdf455fff2 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/buildinfo/BuildInfoIntegrationTests-defaultValues.gradle +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/buildinfo/BuildInfoIntegrationTests-defaultValues.gradle @@ -2,8 +2,4 @@ plugins { id 'org.springframework.boot' version '{version}' apply false } -def property(String name, Object defaultValue) { - project.hasProperty(name) ? project.getProperty(name) : defaultValue -} - task buildInfo(type: org.springframework.boot.gradle.tasks.buildinfo.BuildInfo) diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/buildinfo/BuildInfoIntegrationTests-notUpToDateWhenExecutedTwiceAsTimeChanges.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/buildinfo/BuildInfoIntegrationTests-notUpToDateWhenExecutedTwiceAsTimeChanges.gradle new file mode 100644 index 0000000000..cdf455fff2 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/buildinfo/BuildInfoIntegrationTests-notUpToDateWhenExecutedTwiceAsTimeChanges.gradle @@ -0,0 +1,5 @@ +plugins { + id 'org.springframework.boot' version '{version}' apply false +} + +task buildInfo(type: org.springframework.boot.gradle.tasks.buildinfo.BuildInfo) diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/buildinfo/BuildInfoIntegrationTests-notUpToDateWhenExecutedTwiceWithFixedTimeAndChangedGradlePropertiesProjectVersion.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/buildinfo/BuildInfoIntegrationTests-notUpToDateWhenExecutedTwiceWithFixedTimeAndChangedGradlePropertiesProjectVersion.gradle new file mode 100644 index 0000000000..3193b71f3c --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/buildinfo/BuildInfoIntegrationTests-notUpToDateWhenExecutedTwiceWithFixedTimeAndChangedGradlePropertiesProjectVersion.gradle @@ -0,0 +1,13 @@ +plugins { + id 'org.springframework.boot' version '{version}' apply false +} + +task buildInfo(type: org.springframework.boot.gradle.tasks.buildinfo.BuildInfo) { + properties { + artifact = 'example' + group = 'com.example' + name = 'example' + additional = ['additional': 'alpha'] + time = null + } +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/buildinfo/BuildInfoIntegrationTests-notUpToDateWhenExecutedTwiceWithFixedTimeAndChangedProjectVersion.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/buildinfo/BuildInfoIntegrationTests-notUpToDateWhenExecutedTwiceWithFixedTimeAndChangedProjectVersion.gradle new file mode 100644 index 0000000000..60b6b3b2df --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/buildinfo/BuildInfoIntegrationTests-notUpToDateWhenExecutedTwiceWithFixedTimeAndChangedProjectVersion.gradle @@ -0,0 +1,15 @@ +plugins { + id 'org.springframework.boot' version '{version}' apply false +} + +version = '{projectVersion}' + +task buildInfo(type: org.springframework.boot.gradle.tasks.buildinfo.BuildInfo) { + properties { + artifact = 'example' + group = 'com.example' + name = 'example' + additional = ['additional': 'alpha'] + time = null + } +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/buildinfo/BuildInfoIntegrationTests-reproducibleOutputWithFixedTime.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/buildinfo/BuildInfoIntegrationTests-reproducibleOutputWithFixedTime.gradle new file mode 100644 index 0000000000..5d529839a1 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/buildinfo/BuildInfoIntegrationTests-reproducibleOutputWithFixedTime.gradle @@ -0,0 +1,9 @@ +plugins { + id 'org.springframework.boot' version '{version}' apply false +} + +task buildInfo(type: org.springframework.boot.gradle.tasks.buildinfo.BuildInfo) { + properties { + time = null + } +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/buildinfo/BuildInfoIntegrationTests-upToDateWhenExecutedTwiceWithFixedTime.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/buildinfo/BuildInfoIntegrationTests-upToDateWhenExecutedTwiceWithFixedTime.gradle new file mode 100644 index 0000000000..5d529839a1 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/buildinfo/BuildInfoIntegrationTests-upToDateWhenExecutedTwiceWithFixedTime.gradle @@ -0,0 +1,9 @@ +plugins { + id 'org.springframework.boot' version '{version}' apply false +} + +task buildInfo(type: org.springframework.boot.gradle.tasks.buildinfo.BuildInfo) { + properties { + time = null + } +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/buildinfo/BuildInfoIntegrationTests.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/buildinfo/BuildInfoIntegrationTests.gradle deleted file mode 100644 index 2e242db579..0000000000 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/buildinfo/BuildInfoIntegrationTests.gradle +++ /dev/null @@ -1,22 +0,0 @@ -plugins { - id 'org.springframework.boot' version '{version}' apply false -} - -def property(String name, Object defaultValue) { - project.hasProperty(name) ? project.getProperty(name) : defaultValue -} - -version = property('projectVersion', '0.1.0') - -task buildInfo(type: org.springframework.boot.gradle.tasks.buildinfo.BuildInfo) { - destinationDir file(property('buildInfoDestinationDir', project.buildDir)) - properties { - artifact = property('buildInfoArtifact', 'foo') - group = property('buildInfoGroup', 'foo') - name = property('buildInfoName', 'foo') - additional = ['additional': property('buildInfoAdditional', 'foo')] - if (project.hasProperty('nullTime')) { - time = null - } - } -}