Update BuildInfo to support Gradle's configuration cache

See gh-22922
pull/23886/head
Andy Wilkinson 4 years ago
parent 83cfd3b2e6
commit d1f543fc1d

@ -24,6 +24,7 @@ import java.util.Map;
import org.gradle.api.Action; import org.gradle.api.Action;
import org.gradle.api.Project; import org.gradle.api.Project;
import org.gradle.api.Task; import org.gradle.api.Task;
import org.gradle.api.file.DirectoryProperty;
import org.gradle.api.internal.ConventionTask; import org.gradle.api.internal.ConventionTask;
import org.gradle.api.tasks.Nested; import org.gradle.api.tasks.Nested;
import org.gradle.api.tasks.OutputDirectory; import org.gradle.api.tasks.OutputDirectory;
@ -44,7 +45,12 @@ public class BuildInfo extends ConventionTask {
private final BuildInfoProperties properties = new BuildInfoProperties(getProject()); 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 * Generates the {@code build-info.properties} file in the configured
@ -73,7 +79,7 @@ public class BuildInfo extends ConventionTask {
*/ */
@OutputDirectory @OutputDirectory
public File getDestinationDir() { 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 * @param destinationDir the destination directory
*/ */
public void setDestinationDir(File destinationDir) { public void setDestinationDir(File destinationDir) {
this.destinationDir = destinationDir; this.destinationDir.set(destinationDir);
} }
/** /**

@ -16,6 +16,8 @@
package org.springframework.boot.gradle.tasks.buildinfo; package org.springframework.boot.gradle.tasks.buildinfo;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable; import java.io.Serializable;
import java.time.Instant; import java.time.Instant;
import java.util.HashMap; import java.util.HashMap;
@ -23,6 +25,7 @@ import java.util.Map;
import org.gradle.api.Project; import org.gradle.api.Project;
import org.gradle.api.provider.Property; import org.gradle.api.provider.Property;
import org.gradle.api.provider.Provider;
import org.gradle.api.tasks.Input; import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.Optional; import org.gradle.api.tasks.Optional;
@ -35,6 +38,8 @@ import org.gradle.api.tasks.Optional;
@SuppressWarnings("serial") @SuppressWarnings("serial")
public class BuildInfoProperties implements Serializable { public class BuildInfoProperties implements Serializable {
private transient Instant creationTime = Instant.now();
private final Property<String> group; private final Property<String> group;
private final Property<String> artifact; private final Property<String> artifact;
@ -43,22 +48,35 @@ public class BuildInfoProperties implements Serializable {
private final Property<String> name; private final Property<String> name;
private final Property<Instant> time; private final Property<Long> time;
private boolean timeConfigured = false;
private Map<String, Object> additionalProperties = new HashMap<>(); private Map<String, Object> additionalProperties = new HashMap<>();
BuildInfoProperties(Project project) { BuildInfoProperties(Project project) {
this.time = project.getObjects().property(Instant.class); this.time = project.getObjects().property(Long.class);
this.time.set(Instant.now());
this.group = project.getObjects().property(String.class); this.group = project.getObjects().property(String.class);
this.group.set(project.provider(() -> project.getGroup().toString())); this.group.set(project.provider(() -> project.getGroup().toString()));
this.artifact = project.getObjects().property(String.class); this.artifact = project.getObjects().property(String.class);
this.version = 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 = project.getObjects().property(String.class);
this.name.set(project.provider(project::getName)); this.name.set(project.provider(project::getName));
} }
private Provider<String> projectVersion(Project project) {
try {
Provider<String> 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 * Returns the value used for the {@code build.group} property. Defaults to the
* {@link Project#getGroup() Project's group}. * {@link Project#getGroup() Project's group}.
@ -142,7 +160,14 @@ public class BuildInfoProperties implements Serializable {
@Input @Input
@Optional @Optional
public Instant getTime() { 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 * @param time the build time
*/ */
public void setTime(Instant 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; this.additionalProperties = additionalProperties;
} }
private void readObject(ObjectInputStream input) throws ClassNotFoundException, IOException {
input.defaultReadObject();
this.creationTime = Instant.now();
}
} }

@ -19,9 +19,13 @@ package org.springframework.boot.gradle.tasks.buildinfo;
import java.io.File; import java.io.File;
import java.io.FileReader; import java.io.FileReader;
import java.io.IOException; 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 java.util.Properties;
import org.gradle.testkit.runner.BuildResult;
import org.gradle.testkit.runner.InvalidRunnerConfigurationException; import org.gradle.testkit.runner.InvalidRunnerConfigurationException;
import org.gradle.testkit.runner.TaskOutcome; import org.gradle.testkit.runner.TaskOutcome;
import org.gradle.testkit.runner.UnexpectedBuildFailure; import org.gradle.testkit.runner.UnexpectedBuildFailure;
@ -38,7 +42,7 @@ import static org.assertj.core.api.Assertions.assertThat;
* *
* @author Andy Wilkinson * @author Andy Wilkinson
*/ */
@GradleCompatibility @GradleCompatibility(configurationCache = true)
class BuildInfoIntegrationTests { class BuildInfoIntegrationTests {
GradleBuild gradleBuild; GradleBuild gradleBuild;
@ -69,7 +73,14 @@ class BuildInfoIntegrationTests {
@TestTemplate @TestTemplate
void notUpToDateWhenExecutedTwiceAsTimeChanges() { void notUpToDateWhenExecutedTwiceAsTimeChanges() {
assertThat(this.gradleBuild.build("buildInfo").task(":buildInfo").getOutcome()).isEqualTo(TaskOutcome.SUCCESS); 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); 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 @TestTemplate
@ -81,11 +92,22 @@ class BuildInfoIntegrationTests {
} }
@TestTemplate @TestTemplate
void notUpToDateWhenExecutedTwiceWithFixedTimeAndChangedProjectVersion() { void notUpToDateWhenExecutedTwiceWithFixedTimeAndChangedProjectVersion() throws IOException {
assertThat(this.gradleBuild.build("buildInfo", "-PnullTime").task(":buildInfo").getOutcome()) assertThat(this.gradleBuild.scriptProperty("projectVersion", "0.1.0").build("buildInfo").task(":buildInfo")
.isEqualTo(TaskOutcome.SUCCESS); .getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
BuildResult result = this.gradleBuild.build("buildInfo", "-PnullTime", "-PprojectVersion=0.2.0"); assertThat(this.gradleBuild.scriptProperty("projectVersion", "0.2.0").build("buildInfo").task(":buildInfo")
assertThat(result.task(":buildInfo").getOutcome()).isEqualTo(TaskOutcome.SUCCESS); .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 @TestTemplate

@ -24,6 +24,8 @@ import java.time.format.DateTimeFormatter;
import java.util.Properties; import java.util.Properties;
import org.gradle.api.Project; import org.gradle.api.Project;
import org.gradle.api.internal.project.ProjectInternal;
import org.gradle.initialization.GradlePropertiesController;
import org.gradle.testfixtures.ProjectBuilder; import org.gradle.testfixtures.ProjectBuilder;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.api.io.TempDir;
@ -125,7 +127,10 @@ class BuildInfoTests {
private Project createProject(String projectName) { private Project createProject(String projectName) {
File projectDir = new File(this.temp, 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) { private BuildInfo createTask(Project project) {

@ -176,7 +176,7 @@ public class GradleBuild {
new File(this.projectDir, "repository")); new File(this.projectDir, "repository"));
GradleRunner gradleRunner = GradleRunner.create().withProjectDir(this.projectDir) GradleRunner gradleRunner = GradleRunner.create().withProjectDir(this.projectDir)
.withPluginClasspath(pluginClasspath()); .withPluginClasspath(pluginClasspath());
if (this.dsl != Dsl.KOTLIN) { if (this.dsl != Dsl.KOTLIN && !this.configurationCache) {
// see https://github.com/gradle/gradle/issues/6862 // see https://github.com/gradle/gradle/issues/6862
gradleRunner.withDebug(true); gradleRunner.withDebug(true);
} }

@ -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']
}
}

@ -2,8 +2,4 @@ plugins {
id 'org.springframework.boot' version '{version}' apply false 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) task buildInfo(type: org.springframework.boot.gradle.tasks.buildinfo.BuildInfo)

@ -0,0 +1,5 @@
plugins {
id 'org.springframework.boot' version '{version}' apply false
}
task buildInfo(type: org.springframework.boot.gradle.tasks.buildinfo.BuildInfo)

@ -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
}
}

@ -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
}
}

@ -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
}
}

@ -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
}
}

@ -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
}
}
}
Loading…
Cancel
Save