From d136324b993db016b23c40ae7ea680f00f790cef Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Thu, 1 Oct 2020 14:13:14 +0100 Subject: [PATCH 1/9] Add support for testing the Gradle plugin with --configuration-cache See gh-22922 --- .../gradle/junit/GradleCompatibility.java | 7 ++++ .../junit/GradleCompatibilityExtension.java | 35 ++++++++++++++----- .../boot/gradle/testkit/GradleBuild.java | 10 ++++++ 3 files changed, 44 insertions(+), 8 deletions(-) diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/junit/GradleCompatibility.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/junit/GradleCompatibility.java index 28cc5b7067..73fe2698f0 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/junit/GradleCompatibility.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/junit/GradleCompatibility.java @@ -41,4 +41,11 @@ import org.springframework.boot.gradle.testkit.GradleBuild; @ExtendWith(GradleCompatibilityExtension.class) public @interface GradleCompatibility { + /** + * Whether to include running Gradle with {@code --cache-configuration} cache in the + * compatibility matrix. + * @return {@code true} to enable the configuration cache, {@code false} otherwise + */ + boolean configurationCache() default false; + } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/junit/GradleCompatibilityExtension.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/junit/GradleCompatibilityExtension.java index e492e6b43e..91950952c6 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/junit/GradleCompatibilityExtension.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/junit/GradleCompatibilityExtension.java @@ -16,16 +16,19 @@ package org.springframework.boot.gradle.junit; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.stream.Stream; import org.gradle.api.JavaVersion; +import org.gradle.util.GradleVersion; import org.junit.jupiter.api.TestTemplate; import org.junit.jupiter.api.extension.Extension; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.TestTemplateInvocationContext; import org.junit.jupiter.api.extension.TestTemplateInvocationContextProvider; +import org.junit.platform.commons.util.AnnotationUtils; import org.springframework.boot.gradle.testkit.GradleBuild; import org.springframework.boot.gradle.testkit.GradleBuildExtension; @@ -45,16 +48,29 @@ final class GradleCompatibilityExtension implements TestTemplateInvocationContex JavaVersion javaVersion = JavaVersion.current(); if (javaVersion.isCompatibleWith(JavaVersion.VERSION_14) || javaVersion.isCompatibleWith(JavaVersion.VERSION_13)) { - GRADLE_VERSIONS = Arrays.asList("6.3", "6.4.1", "6.5.1", "6.6.1", "default"); + GRADLE_VERSIONS = Arrays.asList("6.3", "6.4.1", "6.5.1", "6.6.1", "current"); } else { - GRADLE_VERSIONS = Arrays.asList("5.6.4", "6.3", "6.4.1", "6.5.1", "6.6.1", "default"); + GRADLE_VERSIONS = Arrays.asList("5.6.4", "6.3", "6.4.1", "6.5.1", "6.6.1", "current"); } } @Override public Stream provideTestTemplateInvocationContexts(ExtensionContext context) { - return GRADLE_VERSIONS.stream().map(GradleVersionTestTemplateInvocationContext::new); + return GRADLE_VERSIONS.stream().flatMap((version) -> { + if (version.equals("current")) { + version = GradleVersion.current().getVersion(); + } + List invocationContexts = new ArrayList<>(); + invocationContexts.add(new GradleVersionTestTemplateInvocationContext(version, false)); + boolean configurationCache = AnnotationUtils + .findAnnotation(context.getRequiredTestClass(), GradleCompatibility.class).get() + .configurationCache(); + if (configurationCache && GradleVersion.version(version).compareTo(GradleVersion.version("6.6")) >= 0) { + invocationContexts.add(new GradleVersionTestTemplateInvocationContext(version, true)); + } + return invocationContexts.stream(); + }); } @Override @@ -66,20 +82,23 @@ final class GradleCompatibilityExtension implements TestTemplateInvocationContex private final String gradleVersion; - GradleVersionTestTemplateInvocationContext(String gradleVersion) { + private final boolean configurationCache; + + GradleVersionTestTemplateInvocationContext(String gradleVersion, boolean configurationCache) { this.gradleVersion = gradleVersion; + this.configurationCache = configurationCache; } @Override public String getDisplayName(int invocationIndex) { - return "Gradle " + this.gradleVersion; + return "Gradle " + this.gradleVersion + ((this.configurationCache) ? " --configuration-cache" : ""); } @Override public List getAdditionalExtensions() { - GradleBuild gradleBuild = new GradleBuild(); - if (!this.gradleVersion.equals("default")) { - gradleBuild.gradleVersion(this.gradleVersion); + GradleBuild gradleBuild = new GradleBuild().gradleVersion(this.gradleVersion); + if (this.configurationCache) { + gradleBuild.configurationCache(); } return Arrays.asList(new GradleBuildFieldSetter(gradleBuild), new GradleBuildExtension()); } 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 41522140ac..1f9a3da743 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 @@ -72,6 +72,8 @@ public class GradleBuild { private GradleVersion expectDeprecationWarnings; + private boolean configurationCache = false; + public GradleBuild() { this(Dsl.GROOVY); } @@ -124,6 +126,11 @@ public class GradleBuild { return this; } + public GradleBuild configurationCache() { + this.configurationCache = true; + return this; + } + public BuildResult build(String... arguments) { try { BuildResult result = prepareRunner(arguments).build(); @@ -169,6 +176,9 @@ public class GradleBuild { allArguments.addAll(Arrays.asList(arguments)); allArguments.add("--warning-mode"); allArguments.add("all"); + if (this.configurationCache) { + allArguments.add("--configuration-cache"); + } return gradleRunner.withArguments(allArguments); } From c828521912a590a30b785b9b124a2c02ca4d3e08 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Thu, 1 Oct 2020 15:16:07 +0100 Subject: [PATCH 2/9] Allow tests to provide properties that modify the build.gradle script See gh-22922 --- .../boot/gradle/testkit/GradleBuild.java | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) 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 1f9a3da743..439e6d661d 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 @@ -24,7 +24,10 @@ import java.net.URL; import java.nio.file.Files; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import java.util.Map.Entry; import java.util.jar.JarFile; import com.fasterxml.jackson.annotation.JsonView; @@ -74,8 +77,12 @@ public class GradleBuild { private boolean configurationCache = false; + private Map scriptProperties = new HashMap<>(); + public GradleBuild() { this(Dsl.GROOVY); + this.scriptProperties.put("bootVersion", getBootVersion()); + this.scriptProperties.put("dependencyManagementPluginVersion", getDependencyManagementPluginVersion()); } public GradleBuild(Dsl dsl) { @@ -131,6 +138,11 @@ public class GradleBuild { return this; } + public GradleBuild scriptProperty(String key, String value) { + this.scriptProperties.put(key, value); + return this; + } + public BuildResult build(String... arguments) { try { BuildResult result = prepareRunner(arguments).build(); @@ -155,9 +167,10 @@ public class GradleBuild { } public GradleRunner prepareRunner(String... arguments) throws IOException { - String scriptContent = FileCopyUtils.copyToString(new FileReader(this.script)) - .replace("{version}", getBootVersion()) - .replace("{dependency-management-plugin-version}", getDependencyManagementPluginVersion()); + String scriptContent = FileCopyUtils.copyToString(new FileReader(this.script)); + for (Entry property : this.scriptProperties.entrySet()) { + scriptContent = scriptContent.replace("{" + property.getKey() + "}", property.getValue()); + } FileCopyUtils.copy(scriptContent, new FileWriter(new File(this.projectDir, "build" + this.dsl.getExtension()))); FileSystemUtils.copyRecursively(new File("src/test/resources/repository"), new File(this.projectDir, "repository")); From 081ef2d905089b8ecf0e91a119fe87a6e4ef4340 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Thu, 1 Oct 2020 15:21:08 +0100 Subject: [PATCH 3/9] Update BootJar to support Gradle's configuration cache See gh-22922 --- .../boot/gradle/tasks/bundling/BootJar.java | 21 +++++++++++- .../AbstractBootArchiveIntegrationTests.java | 18 +++++----- .../bundling/BootJarIntegrationTests.java | 33 +++++++++---------- .../bundling/BootWarIntegrationTests.java | 3 ++ .../BootJarIntegrationTests-basicBuild.gradle | 8 +++++ ...hLayerToolsAndThenWithoutLayerTools.gradle | 11 +++++++ ...BuiltWithoutLayersAndThenWithLayers.gradle | 11 +++++++ ...DateWhenLaunchScriptPropertyChanges.gradle | 11 +++++++ ...riptWasIncludedAndThenIsNotIncluded.gradle | 9 +++++ ...riptWasNotIncludedAndThenIsIncluded.gradle | 9 +++++ ...grationTests-upToDateWhenBuiltTwice.gradle | 8 +++++ ...nBuiltTwiceWithLaunchScriptIncluded.gradle | 9 +++++ ...ltLayeredAndThenWithExplicitLayered.gradle | 9 +++++ .../bundling/BootJarIntegrationTests.gradle | 19 ----------- ...DateWhenLaunchScriptPropertyChanges.gradle | 11 +++++++ ...riptWasIncludedAndThenIsNotIncluded.gradle | 9 +++++ ...riptWasNotIncludedAndThenIsIncluded.gradle | 9 +++++ 17 files changed, 160 insertions(+), 48 deletions(-) create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-basicBuild.gradle create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-notUpToDateWhenBuiltWithLayerToolsAndThenWithoutLayerTools.gradle create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-notUpToDateWhenBuiltWithoutLayersAndThenWithLayers.gradle create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-notUpToDateWhenLaunchScriptPropertyChanges.gradle create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-notUpToDateWhenLaunchScriptWasIncludedAndThenIsNotIncluded.gradle create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-notUpToDateWhenLaunchScriptWasNotIncludedAndThenIsIncluded.gradle create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-upToDateWhenBuiltTwice.gradle create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-upToDateWhenBuiltTwiceWithLaunchScriptIncluded.gradle create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-upToDateWhenBuiltWithDefaultLayeredAndThenWithExplicitLayered.gradle delete mode 100644 spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests.gradle create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-notUpToDateWhenLaunchScriptPropertyChanges.gradle create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-notUpToDateWhenLaunchScriptWasIncludedAndThenIsNotIncluded.gradle create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-notUpToDateWhenLaunchScriptWasNotIncludedAndThenIsIncluded.gradle diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootJar.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootJar.java index 4686b4348b..ea483ff099 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootJar.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootJar.java @@ -19,6 +19,7 @@ package org.springframework.boot.gradle.tasks.bundling; import java.io.File; import java.util.Collections; import java.util.concurrent.Callable; +import java.util.function.Function; import org.gradle.api.Action; import org.gradle.api.Project; @@ -72,7 +73,7 @@ public class BootJar extends Jar implements BootArchive { * Creates a new {@code BootJar} task. */ public BootJar() { - this.support = new BootArchiveSupport(LAUNCHER, this::isLibrary, this::resolveZipCompression); + this.support = new BootArchiveSupport(LAUNCHER, new LibrarySpec(), new ZipCompressionResolver()); this.bootInfSpec = getProject().copySpec().into("BOOT-INF"); this.mainClass = getProject().getObjects().property(String.class); configureBootInfSpec(this.bootInfSpec); @@ -325,4 +326,22 @@ public class BootJar extends Jar implements BootArchive { return this.resolvedDependencies; } + private final class LibrarySpec implements Spec { + + @Override + public boolean isSatisfiedBy(FileCopyDetails details) { + return isLibrary(details); + } + + } + + private final class ZipCompressionResolver implements Function { + + @Override + public ZipCompression apply(FileCopyDetails details) { + return resolveZipCompression(details); + } + + } + } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/AbstractBootArchiveIntegrationTests.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/AbstractBootArchiveIntegrationTests.java index 8912a01647..c4e2476281 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/AbstractBootArchiveIntegrationTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/AbstractBootArchiveIntegrationTests.java @@ -32,7 +32,6 @@ import org.gradle.testkit.runner.TaskOutcome; import org.gradle.testkit.runner.UnexpectedBuildFailure; import org.junit.jupiter.api.TestTemplate; -import org.springframework.boot.gradle.junit.GradleCompatibility; import org.springframework.boot.gradle.testkit.GradleBuild; import org.springframework.boot.loader.tools.FileUtils; @@ -43,7 +42,6 @@ import static org.assertj.core.api.Assertions.assertThat; * * @author Andy Wilkinson */ -@GradleCompatibility abstract class AbstractBootArchiveIntegrationTests { private final String taskName; @@ -99,25 +97,25 @@ abstract class AbstractBootArchiveIntegrationTests { @TestTemplate void notUpToDateWhenLaunchScriptWasNotIncludedAndThenIsIncluded() { - assertThat(this.gradleBuild.build(this.taskName).task(":" + this.taskName).getOutcome()) - .isEqualTo(TaskOutcome.SUCCESS); - assertThat(this.gradleBuild.build("-PincludeLaunchScript=true", this.taskName).task(":" + this.taskName) + assertThat(this.gradleBuild.scriptProperty("launchScript", "").build(this.taskName).task(":" + this.taskName) .getOutcome()).isEqualTo(TaskOutcome.SUCCESS); + assertThat(this.gradleBuild.scriptProperty("launchScript", "launchScript()").build(this.taskName) + .task(":" + this.taskName).getOutcome()).isEqualTo(TaskOutcome.SUCCESS); } @TestTemplate void notUpToDateWhenLaunchScriptWasIncludedAndThenIsNotIncluded() { - assertThat(this.gradleBuild.build(this.taskName).task(":" + this.taskName).getOutcome()) - .isEqualTo(TaskOutcome.SUCCESS); - assertThat(this.gradleBuild.build("-PincludeLaunchScript=true", this.taskName).task(":" + this.taskName) + assertThat(this.gradleBuild.scriptProperty("launchScript", "launchScript()").build(this.taskName) + .task(":" + this.taskName).getOutcome()).isEqualTo(TaskOutcome.SUCCESS); + assertThat(this.gradleBuild.scriptProperty("launchScript", "").build(this.taskName).task(":" + this.taskName) .getOutcome()).isEqualTo(TaskOutcome.SUCCESS); } @TestTemplate void notUpToDateWhenLaunchScriptPropertyChanges() { - assertThat(this.gradleBuild.build("-PincludeLaunchScript=true", "-PlaunchScriptProperty=foo", this.taskName) + assertThat(this.gradleBuild.scriptProperty("launchScriptProperty", "alpha").build(this.taskName) .task(":" + this.taskName).getOutcome()).isEqualTo(TaskOutcome.SUCCESS); - assertThat(this.gradleBuild.build("-PincludeLaunchScript=true", "-PlaunchScriptProperty=bar", this.taskName) + assertThat(this.gradleBuild.scriptProperty("launchScriptProperty", "bravo").build(this.taskName) .task(":" + this.taskName).getOutcome()).isEqualTo(TaskOutcome.SUCCESS); } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests.java index 476af25b15..361ca73c7c 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests.java @@ -43,6 +43,7 @@ import org.gradle.testkit.runner.TaskOutcome; import org.gradle.testkit.runner.UnexpectedBuildFailure; import org.junit.jupiter.api.TestTemplate; +import org.springframework.boot.gradle.junit.GradleCompatibility; import org.springframework.boot.loader.tools.JarModeLibrary; import org.springframework.util.StringUtils; @@ -55,43 +56,39 @@ import static org.assertj.core.api.Assertions.assertThat; * @author Madhura Bhave * @author Paddy Drury */ +@GradleCompatibility(configurationCache = true) class BootJarIntegrationTests extends AbstractBootArchiveIntegrationTests { BootJarIntegrationTests() { super("bootJar", "BOOT-INF/lib/", "BOOT-INF/classes/"); } - @TestTemplate - void upToDateWhenBuiltTwiceWithLayers() throws InvalidRunnerConfigurationException, UnexpectedBuildFailure { - assertThat(this.gradleBuild.build("-PcustomizeLayered=true", "bootJar").task(":bootJar").getOutcome()) - .isEqualTo(TaskOutcome.SUCCESS); - assertThat(this.gradleBuild.build("-PcustomizeLayered=true", "bootJar").task(":bootJar").getOutcome()) - .isEqualTo(TaskOutcome.UP_TO_DATE); - } - @TestTemplate void upToDateWhenBuiltWithDefaultLayeredAndThenWithExplicitLayered() throws InvalidRunnerConfigurationException, UnexpectedBuildFailure { - assertThat(this.gradleBuild.build("bootJar").task(":bootJar").getOutcome()).isEqualTo(TaskOutcome.SUCCESS); - assertThat(this.gradleBuild.build("-PcustomizeLayered=true", "bootJar").task(":bootJar").getOutcome()) - .isEqualTo(TaskOutcome.UP_TO_DATE); + assertThat(this.gradleBuild.scriptProperty("layered", "").build("bootJar").task(":bootJar").getOutcome()) + .isEqualTo(TaskOutcome.SUCCESS); + assertThat( + this.gradleBuild.scriptProperty("layered", "layered {}").build("bootJar").task(":bootJar").getOutcome()) + .isEqualTo(TaskOutcome.UP_TO_DATE); } @TestTemplate void notUpToDateWhenBuiltWithoutLayersAndThenWithLayers() throws InvalidRunnerConfigurationException, UnexpectedBuildFailure { - assertThat(this.gradleBuild.build("-PcustomizeLayered=true", "-PdisableLayers=true", "bootJar").task(":bootJar") - .getOutcome()).isEqualTo(TaskOutcome.SUCCESS); - assertThat(this.gradleBuild.build("-PcustomizeLayered=true", "bootJar").task(":bootJar").getOutcome()) - .isEqualTo(TaskOutcome.SUCCESS); + assertThat(this.gradleBuild.scriptProperty("layerEnablement", "enabled = false").build("bootJar") + .task(":bootJar").getOutcome()).isEqualTo(TaskOutcome.SUCCESS); + assertThat(this.gradleBuild.scriptProperty("layerEnablement", "enabled = true").build("bootJar") + .task(":bootJar").getOutcome()).isEqualTo(TaskOutcome.SUCCESS); } @TestTemplate void notUpToDateWhenBuiltWithLayerToolsAndThenWithoutLayerTools() throws InvalidRunnerConfigurationException, UnexpectedBuildFailure { - assertThat(this.gradleBuild.build("bootJar").task(":bootJar").getOutcome()).isEqualTo(TaskOutcome.SUCCESS); - assertThat(this.gradleBuild.build("-PcustomizeLayered=true", "-PexcludeTools=true", "bootJar").task(":bootJar") - .getOutcome()).isEqualTo(TaskOutcome.SUCCESS); + assertThat(this.gradleBuild.scriptProperty("layerTools", "").build("bootJar").task(":bootJar").getOutcome()) + .isEqualTo(TaskOutcome.SUCCESS); + assertThat(this.gradleBuild.scriptProperty("layerTools", "includeLayerTools = false").build("bootJar") + .task(":bootJar").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/bundling/BootWarIntegrationTests.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests.java index aa6375bfb2..32bfef20e9 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests.java @@ -16,11 +16,14 @@ package org.springframework.boot.gradle.tasks.bundling; +import org.springframework.boot.gradle.junit.GradleCompatibility; + /** * Integration tests for {@link BootJar}. * * @author Andy Wilkinson */ +@GradleCompatibility class BootWarIntegrationTests extends AbstractBootArchiveIntegrationTests { BootWarIntegrationTests() { diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-basicBuild.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-basicBuild.gradle new file mode 100644 index 0000000000..be0e82ef6d --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-basicBuild.gradle @@ -0,0 +1,8 @@ +plugins { + id 'java' + id 'org.springframework.boot' version '{version}' +} + +bootJar { + mainClass = 'com.example.Application' +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-notUpToDateWhenBuiltWithLayerToolsAndThenWithoutLayerTools.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-notUpToDateWhenBuiltWithLayerToolsAndThenWithoutLayerTools.gradle new file mode 100644 index 0000000000..7530a605b9 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-notUpToDateWhenBuiltWithLayerToolsAndThenWithoutLayerTools.gradle @@ -0,0 +1,11 @@ +plugins { + id 'java' + id 'org.springframework.boot' version '{version}' +} + +bootJar { + mainClass = 'com.example.Application' + layered { + {layerTools} + } +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-notUpToDateWhenBuiltWithoutLayersAndThenWithLayers.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-notUpToDateWhenBuiltWithoutLayersAndThenWithLayers.gradle new file mode 100644 index 0000000000..b6b9c39402 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-notUpToDateWhenBuiltWithoutLayersAndThenWithLayers.gradle @@ -0,0 +1,11 @@ +plugins { + id 'java' + id 'org.springframework.boot' version '{version}' +} + +bootJar { + mainClass = 'com.example.Application' + layered { + {layerEnablement} + } +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-notUpToDateWhenLaunchScriptPropertyChanges.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-notUpToDateWhenLaunchScriptPropertyChanges.gradle new file mode 100644 index 0000000000..36e7a1e38b --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-notUpToDateWhenLaunchScriptPropertyChanges.gradle @@ -0,0 +1,11 @@ +plugins { + id 'java' + id 'org.springframework.boot' version '{version}' +} + +bootJar { + mainClass = 'com.example.Application' + launchScript { + properties 'prop' : '{launchScriptProperty}' + } +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-notUpToDateWhenLaunchScriptWasIncludedAndThenIsNotIncluded.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-notUpToDateWhenLaunchScriptWasIncludedAndThenIsNotIncluded.gradle new file mode 100644 index 0000000000..ac286dabae --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-notUpToDateWhenLaunchScriptWasIncludedAndThenIsNotIncluded.gradle @@ -0,0 +1,9 @@ +plugins { + id 'java' + id 'org.springframework.boot' version '{version}' +} + +bootJar { + mainClass = 'com.example.Application' + {launchScript} +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-notUpToDateWhenLaunchScriptWasNotIncludedAndThenIsIncluded.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-notUpToDateWhenLaunchScriptWasNotIncludedAndThenIsIncluded.gradle new file mode 100644 index 0000000000..ac286dabae --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-notUpToDateWhenLaunchScriptWasNotIncludedAndThenIsIncluded.gradle @@ -0,0 +1,9 @@ +plugins { + id 'java' + id 'org.springframework.boot' version '{version}' +} + +bootJar { + mainClass = 'com.example.Application' + {launchScript} +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-upToDateWhenBuiltTwice.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-upToDateWhenBuiltTwice.gradle new file mode 100644 index 0000000000..be0e82ef6d --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-upToDateWhenBuiltTwice.gradle @@ -0,0 +1,8 @@ +plugins { + id 'java' + id 'org.springframework.boot' version '{version}' +} + +bootJar { + mainClass = 'com.example.Application' +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-upToDateWhenBuiltTwiceWithLaunchScriptIncluded.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-upToDateWhenBuiltTwiceWithLaunchScriptIncluded.gradle new file mode 100644 index 0000000000..4d00e61b2a --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-upToDateWhenBuiltTwiceWithLaunchScriptIncluded.gradle @@ -0,0 +1,9 @@ +plugins { + id 'java' + id 'org.springframework.boot' version '{version}' +} + +bootJar { + mainClassName = 'com.example.Application' + launchScript() +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-upToDateWhenBuiltWithDefaultLayeredAndThenWithExplicitLayered.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-upToDateWhenBuiltWithDefaultLayeredAndThenWithExplicitLayered.gradle new file mode 100644 index 0000000000..30f703228b --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-upToDateWhenBuiltWithDefaultLayeredAndThenWithExplicitLayered.gradle @@ -0,0 +1,9 @@ +plugins { + id 'java' + id 'org.springframework.boot' version '{version}' +} + +bootJar { + mainClass = 'com.example.Application' + {layered} +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests.gradle deleted file mode 100644 index 21f090f87c..0000000000 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests.gradle +++ /dev/null @@ -1,19 +0,0 @@ -plugins { - id 'java' - id 'org.springframework.boot' version '{version}' -} - -bootJar { - mainClass = 'com.example.Application' - if (project.hasProperty('includeLaunchScript') ? includeLaunchScript : false) { - launchScript { - properties 'prop' : project.hasProperty('launchScriptProperty') ? launchScriptProperty : 'default' - } - } - if (project.hasProperty('customizeLayered') && project.getProperty('customizeLayered')) { - layered { - includeLayerTools = project.hasProperty('excludeTools') && project.getProperty('excludeTools') ? false : true - enabled = project.hasProperty('disableLayers') && project.getProperty('disableLayers') ? false : true - } - } -} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-notUpToDateWhenLaunchScriptPropertyChanges.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-notUpToDateWhenLaunchScriptPropertyChanges.gradle new file mode 100644 index 0000000000..dfc1eb0b1f --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-notUpToDateWhenLaunchScriptPropertyChanges.gradle @@ -0,0 +1,11 @@ +plugins { + id 'war' + id 'org.springframework.boot' version '{version}' +} + +bootWar { + mainClassName = 'com.example.Application' + launchScript { + properties 'prop' : '{launchScriptProperty}' + } +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-notUpToDateWhenLaunchScriptWasIncludedAndThenIsNotIncluded.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-notUpToDateWhenLaunchScriptWasIncludedAndThenIsNotIncluded.gradle new file mode 100644 index 0000000000..d9b1148f82 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-notUpToDateWhenLaunchScriptWasIncludedAndThenIsNotIncluded.gradle @@ -0,0 +1,9 @@ +plugins { + id 'war' + id 'org.springframework.boot' version '{version}' +} + +bootWar { + mainClassName = 'com.example.Application' + {launchScript} +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-notUpToDateWhenLaunchScriptWasNotIncludedAndThenIsIncluded.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-notUpToDateWhenLaunchScriptWasNotIncludedAndThenIsIncluded.gradle new file mode 100644 index 0000000000..d9b1148f82 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-notUpToDateWhenLaunchScriptWasNotIncludedAndThenIsIncluded.gradle @@ -0,0 +1,9 @@ +plugins { + id 'war' + id 'org.springframework.boot' version '{version}' +} + +bootWar { + mainClassName = 'com.example.Application' + {launchScript} +} From 83cfd3b2e62f639e2729a5d8367cc4ac687a87a3 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Thu, 1 Oct 2020 15:34:00 +0100 Subject: [PATCH 4/9] Update BootWar to support Gradle's configuration cache See gh-22922 --- .../boot/gradle/tasks/bundling/BootWar.java | 21 ++++++++++++++- .../bundling/BootWarIntegrationTests.java | 2 +- ...pplicationPluginMainClassNameIsUsed.gradle | 2 +- .../BootWarIntegrationTests-basicBuild.gradle | 8 ++++++ ...ests-duplicatesAreHandledGracefully.gradle | 27 +++++++++++++++++++ ...DateWhenLaunchScriptPropertyChanges.gradle | 2 +- ...riptWasIncludedAndThenIsNotIncluded.gradle | 2 +- ...riptWasNotIncludedAndThenIsIncluded.gradle | 2 +- ...grationTests-upToDateWhenBuiltTwice.gradle | 8 ++++++ ...nBuiltTwiceWithLaunchScriptIncluded.gradle | 9 +++++++ .../bundling/BootWarIntegrationTests.gradle | 13 --------- 11 files changed, 77 insertions(+), 19 deletions(-) create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-basicBuild.gradle create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-duplicatesAreHandledGracefully.gradle create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-upToDateWhenBuiltTwice.gradle create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-upToDateWhenBuiltTwiceWithLaunchScriptIncluded.gradle delete mode 100644 spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests.gradle diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootWar.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootWar.java index 95e2dd360e..2e23560ada 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootWar.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootWar.java @@ -18,6 +18,7 @@ package org.springframework.boot.gradle.tasks.bundling; import java.util.Collections; import java.util.concurrent.Callable; +import java.util.function.Function; import org.gradle.api.Action; import org.gradle.api.Project; @@ -59,7 +60,7 @@ public class BootWar extends War implements BootArchive { * Creates a new {@code BootWar} task. */ public BootWar() { - this.support = new BootArchiveSupport(LAUNCHER, this::isLibrary, this::resolveZipCompression); + this.support = new BootArchiveSupport(LAUNCHER, new LibrarySpec(), new ZipCompressionResolver()); this.mainClass = getProject().getObjects().property(String.class); getWebInf().into("lib-provided", fromCallTo(this::getProvidedLibFiles)); this.support.moveModuleInfoToRoot(getRootSpec()); @@ -232,4 +233,22 @@ public class BootWar extends War implements BootArchive { return callable; } + private final class LibrarySpec implements Spec { + + @Override + public boolean isSatisfiedBy(FileCopyDetails details) { + return isLibrary(details); + } + + } + + private final class ZipCompressionResolver implements Function { + + @Override + public ZipCompression apply(FileCopyDetails details) { + return resolveZipCompression(details); + } + + } + } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests.java index 32bfef20e9..52ac318bcb 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests.java @@ -23,7 +23,7 @@ import org.springframework.boot.gradle.junit.GradleCompatibility; * * @author Andy Wilkinson */ -@GradleCompatibility +@GradleCompatibility(configurationCache = true) class BootWarIntegrationTests extends AbstractBootArchiveIntegrationTests { BootWarIntegrationTests() { diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-applicationPluginMainClassNameIsUsed.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-applicationPluginMainClassNameIsUsed.gradle index f19f5f82bd..9caeab7f41 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-applicationPluginMainClassNameIsUsed.gradle +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-applicationPluginMainClassNameIsUsed.gradle @@ -4,4 +4,4 @@ plugins { id 'org.springframework.boot' version '{version}' } -mainClassName = 'com.example.CustomMain' +mainClass = 'com.example.CustomMain' diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-basicBuild.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-basicBuild.gradle new file mode 100644 index 0000000000..a9db466bdf --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-basicBuild.gradle @@ -0,0 +1,8 @@ +plugins { + id 'war' + id 'org.springframework.boot' version '{version}' +} + +bootWar { + mainClass = 'com.example.Application' +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-duplicatesAreHandledGracefully.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-duplicatesAreHandledGracefully.gradle new file mode 100644 index 0000000000..3b5c8323b0 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-duplicatesAreHandledGracefully.gradle @@ -0,0 +1,27 @@ +plugins { + id 'war' + id 'org.springframework.boot' version '{version}' +} + +bootWar { + mainClass = 'com.example.CustomMain' + duplicatesStrategy = "exclude" +} + +configurations { + provided +} + +sourceSets.all { + compileClasspath += configurations.provided + runtimeClasspath += configurations.provided +} + +repositories { + mavenCentral() +} + +dependencies { + implementation("org.apache.commons:commons-lang3:3.6") + provided "org.apache.commons:commons-lang3:3.6" +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-notUpToDateWhenLaunchScriptPropertyChanges.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-notUpToDateWhenLaunchScriptPropertyChanges.gradle index dfc1eb0b1f..c8c1bb54ed 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-notUpToDateWhenLaunchScriptPropertyChanges.gradle +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-notUpToDateWhenLaunchScriptPropertyChanges.gradle @@ -4,7 +4,7 @@ plugins { } bootWar { - mainClassName = 'com.example.Application' + mainClass = 'com.example.Application' launchScript { properties 'prop' : '{launchScriptProperty}' } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-notUpToDateWhenLaunchScriptWasIncludedAndThenIsNotIncluded.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-notUpToDateWhenLaunchScriptWasIncludedAndThenIsNotIncluded.gradle index d9b1148f82..66aeab59e5 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-notUpToDateWhenLaunchScriptWasIncludedAndThenIsNotIncluded.gradle +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-notUpToDateWhenLaunchScriptWasIncludedAndThenIsNotIncluded.gradle @@ -4,6 +4,6 @@ plugins { } bootWar { - mainClassName = 'com.example.Application' + mainClass = 'com.example.Application' {launchScript} } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-notUpToDateWhenLaunchScriptWasNotIncludedAndThenIsIncluded.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-notUpToDateWhenLaunchScriptWasNotIncludedAndThenIsIncluded.gradle index d9b1148f82..66aeab59e5 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-notUpToDateWhenLaunchScriptWasNotIncludedAndThenIsIncluded.gradle +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-notUpToDateWhenLaunchScriptWasNotIncludedAndThenIsIncluded.gradle @@ -4,6 +4,6 @@ plugins { } bootWar { - mainClassName = 'com.example.Application' + mainClass = 'com.example.Application' {launchScript} } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-upToDateWhenBuiltTwice.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-upToDateWhenBuiltTwice.gradle new file mode 100644 index 0000000000..a9db466bdf --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-upToDateWhenBuiltTwice.gradle @@ -0,0 +1,8 @@ +plugins { + id 'war' + id 'org.springframework.boot' version '{version}' +} + +bootWar { + mainClass = 'com.example.Application' +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-upToDateWhenBuiltTwiceWithLaunchScriptIncluded.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-upToDateWhenBuiltTwiceWithLaunchScriptIncluded.gradle new file mode 100644 index 0000000000..2f4ccbf474 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-upToDateWhenBuiltTwiceWithLaunchScriptIncluded.gradle @@ -0,0 +1,9 @@ +plugins { + id 'war' + id 'org.springframework.boot' version '{version}' +} + +bootWar { + mainClass = 'com.example.Application' + launchScript() +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests.gradle deleted file mode 100644 index 32d7680985..0000000000 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests.gradle +++ /dev/null @@ -1,13 +0,0 @@ -plugins { - id 'war' - id 'org.springframework.boot' version '{version}' -} - -bootWar { - mainClass = 'com.example.Application' - if (project.hasProperty('includeLaunchScript') ? includeLaunchScript : false) { - launchScript { - properties 'prop' : project.hasProperty('launchScriptProperty') ? launchScriptProperty : 'default' - } - } -} From d1f543fc1dc787d8fbab927ec5f6009f9ce402a9 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Thu, 1 Oct 2020 20:58:22 +0100 Subject: [PATCH 5/9] Update BuildInfo to support Gradle's configuration cache See gh-22922 --- .../gradle/tasks/buildinfo/BuildInfo.java | 12 ++++-- .../tasks/buildinfo/BuildInfoProperties.java | 43 ++++++++++++++++--- .../buildinfo/BuildInfoIntegrationTests.java | 36 +++++++++++++--- .../tasks/buildinfo/BuildInfoTests.java | 7 ++- .../boot/gradle/testkit/GradleBuild.java | 2 +- ...InfoIntegrationTests-basicExecution.gradle | 15 +++++++ ...dInfoIntegrationTests-defaultValues.gradle | 4 -- ...oDateWhenExecutedTwiceAsTimeChanges.gradle | 5 +++ ...angedGradlePropertiesProjectVersion.gradle | 13 ++++++ ...thFixedTimeAndChangedProjectVersion.gradle | 15 +++++++ ...sts-reproducibleOutputWithFixedTime.gradle | 9 ++++ ...oDateWhenExecutedTwiceWithFixedTime.gradle | 9 ++++ .../BuildInfoIntegrationTests.gradle | 22 ---------- 13 files changed, 148 insertions(+), 44 deletions(-) create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/buildinfo/BuildInfoIntegrationTests-basicExecution.gradle create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/buildinfo/BuildInfoIntegrationTests-notUpToDateWhenExecutedTwiceAsTimeChanges.gradle create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/buildinfo/BuildInfoIntegrationTests-notUpToDateWhenExecutedTwiceWithFixedTimeAndChangedGradlePropertiesProjectVersion.gradle create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/buildinfo/BuildInfoIntegrationTests-notUpToDateWhenExecutedTwiceWithFixedTimeAndChangedProjectVersion.gradle create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/buildinfo/BuildInfoIntegrationTests-reproducibleOutputWithFixedTime.gradle create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/buildinfo/BuildInfoIntegrationTests-upToDateWhenExecutedTwiceWithFixedTime.gradle delete mode 100644 spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/buildinfo/BuildInfoIntegrationTests.gradle 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 - } - } -} From c078a48064e1a82535994814fccd2b63aab377e0 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Fri, 2 Oct 2020 15:04:58 +0100 Subject: [PATCH 6/9] Update BootRun to support Gradle's configuration cache See gh-22922 --- .../application-plugin-main-class-name.gradle | 5 - ...lication-plugin-main-class-name.gradle.kts | 6 - .../spring-boot-dsl-main-class-name.gradle | 6 - ...spring-boot-dsl-main-class-name.gradle.kts | 6 - .../boot/gradle/plugin/JavaPluginAction.java | 72 +++++++++- .../gradle/plugin/ResolveMainClassName.java | 131 ++++++++++++++++++ .../boot/gradle/tasks/run/BootRun.java | 7 +- .../com/example/main/CustomMainClass.java | 34 +++++ .../docs/RunningDocumentationTests.java | 31 ++++- .../junit/GradleCompatibilityExtension.java | 3 +- .../tasks/run/BootRunIntegrationTests.java | 24 ++-- ...pplicationPluginMainClassNameIsUsed.gradle | 2 +- ...nMainClassNameIsNotUsedWhenItIsNull.gradle | 9 +- ...pplicationPluginMainClassNameIsUsed.gradle | 6 +- ...ingBootExtensionMainClassNameIsUsed.gradle | 8 +- 15 files changed, 289 insertions(+), 61 deletions(-) create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/ResolveMainClassName.java create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/com/example/main/CustomMainClass.java diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/running/application-plugin-main-class-name.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/running/application-plugin-main-class-name.gradle index 5febf644a1..1ee1d23aba 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/running/application-plugin-main-class-name.gradle +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/running/application-plugin-main-class-name.gradle @@ -10,8 +10,3 @@ application { } // end::main-class[] -task configuredMainClass { - doLast { - println bootRun.main - } -} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/running/application-plugin-main-class-name.gradle.kts b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/running/application-plugin-main-class-name.gradle.kts index 18e854b63f..9f9d02f2b0 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/running/application-plugin-main-class-name.gradle.kts +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/running/application-plugin-main-class-name.gradle.kts @@ -11,9 +11,3 @@ application { mainClass.set("com.example.ExampleApplication") } // end::main-class[] - -task("configuredMainClass") { - doLast { - println(tasks.getByName("bootRun").main) - } -} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/running/spring-boot-dsl-main-class-name.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/running/spring-boot-dsl-main-class-name.gradle index 643ac4af13..6703507d6d 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/running/spring-boot-dsl-main-class-name.gradle +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/running/spring-boot-dsl-main-class-name.gradle @@ -9,9 +9,3 @@ springBoot { mainClass = 'com.example.ExampleApplication' } // end::main-class[] - -task configuredMainClass { - doLast { - println bootRun.main - } -} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/running/spring-boot-dsl-main-class-name.gradle.kts b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/running/spring-boot-dsl-main-class-name.gradle.kts index 0c5688973e..318eb9dfd7 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/running/spring-boot-dsl-main-class-name.gradle.kts +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/gradle/running/spring-boot-dsl-main-class-name.gradle.kts @@ -11,9 +11,3 @@ springBoot { mainClass.set("com.example.ExampleApplication") } // end::main-class[] - -task("configuredMainClass") { - doLast { - println(tasks.getByName("bootRun").main) - } -} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/JavaPluginAction.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/JavaPluginAction.java index 1e4f73b579..df7a917554 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/JavaPluginAction.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/JavaPluginAction.java @@ -17,6 +17,10 @@ package org.springframework.boot.gradle.plugin; import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.Collections; import java.util.List; import java.util.Optional; @@ -33,16 +37,21 @@ import org.gradle.api.attributes.Bundling; import org.gradle.api.attributes.LibraryElements; import org.gradle.api.attributes.Usage; import org.gradle.api.file.FileCollection; +import org.gradle.api.file.RegularFileProperty; import org.gradle.api.internal.artifacts.dsl.LazyPublishArtifact; import org.gradle.api.model.ObjectFactory; import org.gradle.api.plugins.ApplicationPlugin; import org.gradle.api.plugins.BasePlugin; +import org.gradle.api.plugins.Convention; +import org.gradle.api.plugins.JavaApplication; import org.gradle.api.plugins.JavaPlugin; import org.gradle.api.plugins.JavaPluginConvention; +import org.gradle.api.provider.Provider; import org.gradle.api.tasks.SourceSet; import org.gradle.api.tasks.TaskProvider; import org.gradle.api.tasks.compile.JavaCompile; +import org.springframework.boot.gradle.dsl.SpringBootExtension; import org.springframework.boot.gradle.tasks.bundling.BootBuildImage; import org.springframework.boot.gradle.tasks.bundling.BootJar; import org.springframework.boot.gradle.tasks.run.BootRun; @@ -77,7 +86,8 @@ final class JavaPluginAction implements PluginApplicationAction { TaskProvider bootJar = configureBootJarTask(project); configureBootBuildImageTask(project, bootJar); configureArtifactPublication(bootJar); - configureBootRunTask(project); + TaskProvider resolveMainClassName = configureResolveMainClassNameTask(project); + configureBootRunTask(project, resolveMainClassName); configureUtf8Encoding(project); configureParametersCompilerArg(project); configureAdditionalMetadataLocations(project); @@ -92,6 +102,39 @@ final class JavaPluginAction implements PluginApplicationAction { .configure((task) -> task.dependsOn(this.singlePublishedArtifact)); } + private TaskProvider configureResolveMainClassNameTask(Project project) { + Convention convention = project.getConvention(); + return project.getTasks().register("resolveMainClassName", ResolveMainClassName.class, + (resolveMainClassName) -> { + resolveMainClassName.setClasspath( + javaPluginConvention(project).getSourceSets().findByName(SourceSet.MAIN_SOURCE_SET_NAME) + .getRuntimeClasspath().filter(new JarTypeFileSpec())); + resolveMainClassName.getConfiguredMainClassName().convention(project.provider(() -> { + JavaApplication javaApplication = convention.findByType(JavaApplication.class); + String javaApplicationMainClass = null; + if (javaApplication != null) { + try { + javaApplicationMainClass = javaApplication.getMainClass().getOrNull(); + } + catch (NoSuchMethodError ex) { + javaApplicationMainClass = javaApplication.getMainClassName(); + } + } + if (javaApplicationMainClass != null) { + return javaApplicationMainClass; + } + SpringBootExtension springBootExtension = project.getExtensions() + .findByType(SpringBootExtension.class); + if (springBootExtension != null) { + return springBootExtension.getMainClass().getOrNull(); + } + return null; + })); + resolveMainClassName.getOutputFile() + .set(project.getLayout().getBuildDirectory().file("spring-boot-main-class-name")); + }); + } + private TaskProvider configureBootJarTask(Project project) { return project.getTasks().register(SpringBootPlugin.BOOT_JAR_TASK_NAME, BootJar.class, (bootJar) -> { bootJar.setDescription( @@ -129,7 +172,7 @@ final class JavaPluginAction implements PluginApplicationAction { this.singlePublishedArtifact.addCandidate(artifact); } - private void configureBootRunTask(Project project) { + private void configureBootRunTask(Project project, TaskProvider resolveMainClassName) { project.getTasks().register("bootRun", BootRun.class, (run) -> { run.setDescription("Runs this project as a Spring Boot application."); run.setGroup(ApplicationPlugin.APPLICATION_GROUP); @@ -141,7 +184,30 @@ final class JavaPluginAction implements PluginApplicationAction { } return Collections.emptyList(); }); - run.conventionMapping("main", new MainClassConvention(project, run::getClasspath)); + run.dependsOn(resolveMainClassName); + run.getInputs().file(resolveMainClassName.map((task) -> task.getOutputFile())); + try { + run.getMainClass().set(resolveMainClassName.flatMap((task) -> readMainClassName(task.getOutputFile()))); + } + catch (NoSuchMethodError ex) { + run.conventionMapping("main", + () -> resolveMainClassName.flatMap((task) -> readMainClassName(task.getOutputFile())).get()); + } + }); + } + + private Provider readMainClassName(RegularFileProperty outputFile) { + return outputFile.map((file) -> { + Path output = file.getAsFile().toPath(); + if (!Files.exists(output)) { + return null; + } + try { + return new String(Files.readAllBytes(output), StandardCharsets.UTF_8); + } + catch (IOException ex) { + throw new RuntimeException("Failed to read main class name from '" + output + "'"); + } }); } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/ResolveMainClassName.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/ResolveMainClassName.java new file mode 100644 index 0000000000..e32fd195b4 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/ResolveMainClassName.java @@ -0,0 +1,131 @@ +/* + * Copyright 2012-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.gradle.plugin; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.StandardOpenOption; +import java.util.Objects; + +import org.gradle.api.DefaultTask; +import org.gradle.api.InvalidUserDataException; +import org.gradle.api.Task; +import org.gradle.api.file.FileCollection; +import org.gradle.api.file.RegularFileProperty; +import org.gradle.api.provider.Property; +import org.gradle.api.tasks.Classpath; +import org.gradle.api.tasks.Input; +import org.gradle.api.tasks.Optional; +import org.gradle.api.tasks.OutputFile; +import org.gradle.api.tasks.TaskAction; + +import org.springframework.boot.loader.tools.MainClassFinder; + +/** + * {@link Task} for resolving the name of the application's main class. + * + * @author Andy Wilkinson + * @since 2.4 + */ +public class ResolveMainClassName extends DefaultTask { + + private static final String SPRING_BOOT_APPLICATION_CLASS_NAME = "org.springframework.boot.autoconfigure.SpringBootApplication"; + + private final RegularFileProperty outputFile; + + private final Property configuredMainClass; + + private FileCollection classpath; + + /** + * Creates a new instance of the {@code ResolveMainClassName} task. + */ + public ResolveMainClassName() { + this.outputFile = getProject().getObjects().fileProperty(); + this.configuredMainClass = getProject().getObjects().property(String.class); + } + + /** + * Returns the classpath that the task will examine when resolving the main class + * name. + * @return the classpath + */ + @Classpath + public FileCollection getClasspath() { + return this.classpath; + } + + /** + * Sets the classpath that the task will examine when resolving the main class name. + * @param classpath the classpath + */ + public void setClasspath(FileCollection classpath) { + this.classpath = classpath; + } + + /** + * Returns the property for the task's output file that will contain the name of the + * main class. + * @return the output file + */ + @OutputFile + public RegularFileProperty getOutputFile() { + return this.outputFile; + } + + /** + * Returns the property for the explicitly configured main class name that should be + * used in favour of resolving the main class name from the classpath. + * @return the configured main class name property + */ + @Input + @Optional + public Property getConfiguredMainClassName() { + return this.configuredMainClass; + } + + @TaskAction + void resolveAndStoreMainClassName() throws IOException { + String mainClassName = resolveMainClassName(); + File outputFile = this.outputFile.getAsFile().get(); + outputFile.getParentFile().mkdirs(); + Files.write(this.outputFile.get().getAsFile().toPath(), mainClassName.getBytes(StandardCharsets.UTF_8), + StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); + } + + private String resolveMainClassName() { + String configuredMainClass = this.configuredMainClass.getOrNull(); + if (configuredMainClass != null) { + return configuredMainClass; + } + return getClasspath().filter(File::isDirectory).getFiles().stream().map(this::findMainClass) + .filter(Objects::nonNull).findFirst().orElseThrow(() -> new InvalidUserDataException( + "Main class name has not been configured and it could not be resolved")); + } + + private String findMainClass(File file) { + try { + return MainClassFinder.findSingleMainClass(file, SPRING_BOOT_APPLICATION_CLASS_NAME); + } + catch (IOException ex) { + return null; + } + } + +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/run/BootRun.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/run/BootRun.java index 99fe3d5d35..3769e1ffcd 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/run/BootRun.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/run/BootRun.java @@ -16,7 +16,9 @@ package org.springframework.boot.gradle.tasks.run; +import java.io.File; import java.lang.reflect.Method; +import java.util.Set; import org.gradle.api.file.SourceDirectorySet; import org.gradle.api.tasks.Input; @@ -63,8 +65,9 @@ public class BootRun extends JavaExec { * @param sourceSet the source set */ public void sourceResources(SourceSet sourceSet) { - setClasspath(getProject().files(sourceSet.getResources().getSrcDirs(), getClasspath()) - .filter((file) -> !file.equals(sourceSet.getOutput().getResourcesDir()))); + File resourcesDir = sourceSet.getOutput().getResourcesDir(); + Set srcDirs = sourceSet.getResources().getSrcDirs(); + setClasspath(getProject().files(srcDirs, getClasspath()).filter((file) -> !file.equals(resourcesDir))); } @Override diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/com/example/main/CustomMainClass.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/com/example/main/CustomMainClass.java new file mode 100644 index 0000000000..dfa5c64af0 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/com/example/main/CustomMainClass.java @@ -0,0 +1,34 @@ +/* + * Copyright 2012-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.main; + +/** + * Application used for testing {@code BootRun}'s main class configuration. + * + * @author Andy Wilkinson + */ +public class CustomMainClass { + + protected CustomMainClass() { + + } + + public static void main(String[] args) { + System.out.println(CustomMainClass.class.getName()); + } + +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/docs/RunningDocumentationTests.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/docs/RunningDocumentationTests.java index 1f723a494f..e75b905314 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/docs/RunningDocumentationTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/docs/RunningDocumentationTests.java @@ -17,7 +17,9 @@ package org.springframework.boot.gradle.docs; import java.io.File; +import java.io.FileWriter; import java.io.IOException; +import java.io.PrintWriter; import org.junit.jupiter.api.TestTemplate; import org.junit.jupiter.api.condition.DisabledForJreRange; @@ -43,20 +45,23 @@ class RunningDocumentationTests { @TestTemplate @DisabledForJreRange(min = JRE.JAVA_13) void bootRunMain() throws IOException { - assertThat(this.gradleBuild.script("src/docs/gradle/running/boot-run-main").build("configuredMainClass") - .getOutput()).contains("com.example.ExampleApplication"); + writeMainClass(); + assertThat(this.gradleBuild.script("src/docs/gradle/running/boot-run-main").build("bootRun").getOutput()) + .contains("com.example.ExampleApplication"); } @TestTemplate - void applicationPluginMainClassName() { + void applicationPluginMainClassName() throws IOException { + writeMainClass(); assertThat(this.gradleBuild.script("src/docs/gradle/running/application-plugin-main-class-name") - .build("configuredMainClass").getOutput()).contains("com.example.ExampleApplication"); + .build("bootRun").getOutput()).contains("com.example.ExampleApplication"); } @TestTemplate void springBootDslMainClassName() throws IOException { - assertThat(this.gradleBuild.script("src/docs/gradle/running/spring-boot-dsl-main-class-name") - .build("configuredMainClass").getOutput()).contains("com.example.ExampleApplication"); + writeMainClass(); + assertThat(this.gradleBuild.script("src/docs/gradle/running/spring-boot-dsl-main-class-name").build("bootRun") + .getOutput()).contains("com.example.ExampleApplication"); } @TestTemplate @@ -84,4 +89,18 @@ class RunningDocumentationTests { .contains("com.example.property = custom"); } + private void writeMainClass() throws IOException { + File exampleApplication = new File(this.gradleBuild.getProjectDir(), + "src/main/java/com/example/ExampleApplication.java"); + exampleApplication.getParentFile().mkdirs(); + try (PrintWriter writer = new PrintWriter(new FileWriter(exampleApplication))) { + writer.println("package com.example;"); + writer.println("public class ExampleApplication {"); + writer.println(" public static void main(String[] args) {"); + writer.println(" System.out.println(ExampleApplication.class.getName());"); + writer.println(" }"); + writer.println("}"); + } + } + } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/junit/GradleCompatibilityExtension.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/junit/GradleCompatibilityExtension.java index 91950952c6..18fe04cdf7 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/junit/GradleCompatibilityExtension.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/junit/GradleCompatibilityExtension.java @@ -66,7 +66,8 @@ final class GradleCompatibilityExtension implements TestTemplateInvocationContex boolean configurationCache = AnnotationUtils .findAnnotation(context.getRequiredTestClass(), GradleCompatibility.class).get() .configurationCache(); - if (configurationCache && GradleVersion.version(version).compareTo(GradleVersion.version("6.6")) >= 0) { + if (configurationCache + && GradleVersion.version(version).compareTo(GradleVersion.version("6.7-rc-1")) >= 0) { invocationContexts.add(new GradleVersionTestTemplateInvocationContext(version, true)); } return invocationContexts.stream(); diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/run/BootRunIntegrationTests.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/run/BootRunIntegrationTests.java index 1c4b1bf92a..066131ea53 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/run/BootRunIntegrationTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/run/BootRunIntegrationTests.java @@ -40,7 +40,7 @@ import static org.assertj.core.api.Assertions.assertThat; * * @author Andy Wilkinson */ -@GradleCompatibility +@GradleCompatibility(configurationCache = true) class BootRunIntegrationTests { GradleBuild gradleBuild; @@ -68,23 +68,25 @@ class BootRunIntegrationTests { @TestTemplate void springBootExtensionMainClassNameIsUsed() throws IOException { - BuildResult result = this.gradleBuild.build("echoMainClassName"); - assertThat(result.task(":echoMainClassName").getOutcome()).isEqualTo(TaskOutcome.UP_TO_DATE); - assertThat(result.getOutput()).contains("Main class name = com.example.CustomMainClass"); + copyMainClassApplication(); + BuildResult result = this.gradleBuild.build("bootRun"); + assertThat(result.task(":bootRun").getOutcome()).isEqualTo(TaskOutcome.SUCCESS); + assertThat(result.getOutput()).contains("com.example.main.CustomMainClass"); } @TestTemplate void applicationPluginMainClassNameIsUsed() throws IOException { - BuildResult result = this.gradleBuild.build("echoMainClassName"); - assertThat(result.task(":echoMainClassName").getOutcome()).isEqualTo(TaskOutcome.UP_TO_DATE); - assertThat(result.getOutput()).contains("Main class name = com.example.CustomMainClass"); + copyMainClassApplication(); + BuildResult result = this.gradleBuild.build("bootRun"); + assertThat(result.task(":bootRun").getOutcome()).isEqualTo(TaskOutcome.SUCCESS); + assertThat(result.getOutput()).contains("com.example.main.CustomMainClass"); } @TestTemplate void applicationPluginMainClassNameIsNotUsedWhenItIsNull() throws IOException { copyClasspathApplication(); - BuildResult result = this.gradleBuild.build("echoMainClassName"); - assertThat(result.task(":echoMainClassName").getOutcome()).isEqualTo(TaskOutcome.SUCCESS); + BuildResult result = this.gradleBuild.build("bootRun"); + assertThat(result.task(":bootRun").getOutcome()).isEqualTo(TaskOutcome.SUCCESS); assertThat(result.getOutput()).contains("Main class name = com.example.classpath.BootRunClasspathApplication"); } @@ -135,6 +137,10 @@ class BootRunIntegrationTests { assertThat(result.getOutput()).contains("standard.jar").doesNotContain("starter.jar"); } + private void copyMainClassApplication() throws IOException { + copyApplication("main"); + } + private void copyClasspathApplication() throws IOException { copyApplication("classpath"); } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-applicationPluginMainClassNameIsUsed.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-applicationPluginMainClassNameIsUsed.gradle index 9caeab7f41..f19f5f82bd 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-applicationPluginMainClassNameIsUsed.gradle +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-applicationPluginMainClassNameIsUsed.gradle @@ -4,4 +4,4 @@ plugins { id 'org.springframework.boot' version '{version}' } -mainClass = 'com.example.CustomMain' +mainClassName = 'com.example.CustomMain' diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/run/BootRunIntegrationTests-applicationPluginMainClassNameIsNotUsedWhenItIsNull.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/run/BootRunIntegrationTests-applicationPluginMainClassNameIsNotUsedWhenItIsNull.gradle index 3187d8536f..60be6e642a 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/run/BootRunIntegrationTests-applicationPluginMainClassNameIsNotUsedWhenItIsNull.gradle +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/run/BootRunIntegrationTests-applicationPluginMainClassNameIsNotUsedWhenItIsNull.gradle @@ -3,9 +3,8 @@ plugins { id 'org.springframework.boot' version '{version}' } -task echoMainClassName { - dependsOn compileJava - doLast { - println 'Main class name = ' + bootRun.main +bootRun { + doFirst { + println "Main class name = ${bootRun.main}" } -} +} \ No newline at end of file diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/run/BootRunIntegrationTests-applicationPluginMainClassNameIsUsed.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/run/BootRunIntegrationTests-applicationPluginMainClassNameIsUsed.gradle index f1a0ca76db..3e72e7582d 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/run/BootRunIntegrationTests-applicationPluginMainClassNameIsUsed.gradle +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/run/BootRunIntegrationTests-applicationPluginMainClassNameIsUsed.gradle @@ -3,8 +3,4 @@ plugins { id 'org.springframework.boot' version '{version}' } -mainClassName = 'com.example.CustomMainClass' - -task echoMainClassName { - println 'Main class name = ' + bootRun.main -} +mainClassName = 'com.example.main.CustomMainClass' diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/run/BootRunIntegrationTests-springBootExtensionMainClassNameIsUsed.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/run/BootRunIntegrationTests-springBootExtensionMainClassNameIsUsed.gradle index f912258a07..36730c1c29 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/run/BootRunIntegrationTests-springBootExtensionMainClassNameIsUsed.gradle +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/run/BootRunIntegrationTests-springBootExtensionMainClassNameIsUsed.gradle @@ -1,12 +1,8 @@ plugins { - id 'application' + id 'java' id 'org.springframework.boot' version '{version}' } springBoot { - mainClass = 'com.example.CustomMainClass' -} - -task echoMainClassName { - println 'Main class name = ' + bootRun.main + mainClass = 'com.example.main.CustomMainClass' } From b1c4af40812f509a785ba8a6fd1006116ebd517c Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Tue, 13 Oct 2020 10:29:13 +0100 Subject: [PATCH 7/9] Update bootJar and bootWar to use new main class resolution mechanism See gh-22922 --- .../boot/gradle/plugin/JavaPluginAction.java | 106 ++++------------- .../gradle/plugin/MainClassConvention.java | 109 ------------------ .../gradle/plugin/ResolveMainClassName.java | 71 +++++++++++- .../boot/gradle/plugin/WarPluginAction.java | 45 +++++--- .../junit/GradleCompatibilityExtension.java | 3 +- .../plugin/MainClassConventionTests.java | 81 ------------- .../AbstractBootArchiveIntegrationTests.java | 22 ++++ ...rtClassIsSetByResolvingTheMainClass.gradle | 5 + ...rtClassIsSetByResolvingTheMainClass.gradle | 5 + 9 files changed, 152 insertions(+), 295 deletions(-) delete mode 100644 spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/MainClassConvention.java delete mode 100644 spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/plugin/MainClassConventionTests.java create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-startClassIsSetByResolvingTheMainClass.gradle create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-startClassIsSetByResolvingTheMainClass.gradle diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/JavaPluginAction.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/JavaPluginAction.java index df7a917554..ea76eb8e03 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/JavaPluginAction.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/JavaPluginAction.java @@ -17,15 +17,10 @@ package org.springframework.boot.gradle.plugin; import java.io.File; -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; import java.util.Collections; import java.util.List; import java.util.Optional; import java.util.Set; -import java.util.concurrent.Callable; import org.gradle.api.Action; import org.gradle.api.Plugin; @@ -37,13 +32,10 @@ import org.gradle.api.attributes.Bundling; import org.gradle.api.attributes.LibraryElements; import org.gradle.api.attributes.Usage; import org.gradle.api.file.FileCollection; -import org.gradle.api.file.RegularFileProperty; import org.gradle.api.internal.artifacts.dsl.LazyPublishArtifact; import org.gradle.api.model.ObjectFactory; import org.gradle.api.plugins.ApplicationPlugin; import org.gradle.api.plugins.BasePlugin; -import org.gradle.api.plugins.Convention; -import org.gradle.api.plugins.JavaApplication; import org.gradle.api.plugins.JavaPlugin; import org.gradle.api.plugins.JavaPluginConvention; import org.gradle.api.provider.Provider; @@ -51,7 +43,6 @@ import org.gradle.api.tasks.SourceSet; import org.gradle.api.tasks.TaskProvider; import org.gradle.api.tasks.compile.JavaCompile; -import org.springframework.boot.gradle.dsl.SpringBootExtension; import org.springframework.boot.gradle.tasks.bundling.BootBuildImage; import org.springframework.boot.gradle.tasks.bundling.BootJar; import org.springframework.boot.gradle.tasks.run.BootRun; @@ -86,8 +77,7 @@ final class JavaPluginAction implements PluginApplicationAction { TaskProvider bootJar = configureBootJarTask(project); configureBootBuildImageTask(project, bootJar); configureArtifactPublication(bootJar); - TaskProvider resolveMainClassName = configureResolveMainClassNameTask(project); - configureBootRunTask(project, resolveMainClassName); + configureBootRunTask(project); configureUtf8Encoding(project); configureParametersCompilerArg(project); configureAdditionalMetadataLocations(project); @@ -102,59 +92,26 @@ final class JavaPluginAction implements PluginApplicationAction { .configure((task) -> task.dependsOn(this.singlePublishedArtifact)); } - private TaskProvider configureResolveMainClassNameTask(Project project) { - Convention convention = project.getConvention(); - return project.getTasks().register("resolveMainClassName", ResolveMainClassName.class, - (resolveMainClassName) -> { - resolveMainClassName.setClasspath( - javaPluginConvention(project).getSourceSets().findByName(SourceSet.MAIN_SOURCE_SET_NAME) - .getRuntimeClasspath().filter(new JarTypeFileSpec())); - resolveMainClassName.getConfiguredMainClassName().convention(project.provider(() -> { - JavaApplication javaApplication = convention.findByType(JavaApplication.class); - String javaApplicationMainClass = null; - if (javaApplication != null) { - try { - javaApplicationMainClass = javaApplication.getMainClass().getOrNull(); - } - catch (NoSuchMethodError ex) { - javaApplicationMainClass = javaApplication.getMainClassName(); - } - } - if (javaApplicationMainClass != null) { - return javaApplicationMainClass; - } - SpringBootExtension springBootExtension = project.getExtensions() - .findByType(SpringBootExtension.class); - if (springBootExtension != null) { - return springBootExtension.getMainClass().getOrNull(); - } - return null; - })); - resolveMainClassName.getOutputFile() - .set(project.getLayout().getBuildDirectory().file("spring-boot-main-class-name")); - }); - } - private TaskProvider configureBootJarTask(Project project) { + SourceSet mainSourceSet = javaPluginConvention(project).getSourceSets() + .getByName(SourceSet.MAIN_SOURCE_SET_NAME); + Configuration developmentOnly = project.getConfigurations() + .getByName(SpringBootPlugin.DEVELOPMENT_ONLY_CONFIGURATION_NAME); + Configuration productionRuntimeClasspath = project.getConfigurations() + .getByName(SpringBootPlugin.PRODUCTION_RUNTIME_CLASSPATH_NAME); + FileCollection classpath = mainSourceSet.getRuntimeClasspath() + .minus((developmentOnly.minus(productionRuntimeClasspath))).filter(new JarTypeFileSpec()); + TaskProvider resolveMainClassName = ResolveMainClassName + .registerForTask(SpringBootPlugin.BOOT_JAR_TASK_NAME, project, classpath); return project.getTasks().register(SpringBootPlugin.BOOT_JAR_TASK_NAME, BootJar.class, (bootJar) -> { bootJar.setDescription( "Assembles an executable jar archive containing the main classes and their dependencies."); bootJar.setGroup(BasePlugin.BUILD_GROUP); - SourceSet mainSourceSet = javaPluginConvention(project).getSourceSets() - .getByName(SourceSet.MAIN_SOURCE_SET_NAME); - bootJar.classpath((Callable) () -> { - Configuration developmentOnly = project.getConfigurations() - .getByName(SpringBootPlugin.DEVELOPMENT_ONLY_CONFIGURATION_NAME); - Configuration productionRuntimeClasspath = project.getConfigurations() - .getByName(SpringBootPlugin.PRODUCTION_RUNTIME_CLASSPATH_NAME); - return mainSourceSet.getRuntimeClasspath().minus((developmentOnly.minus(productionRuntimeClasspath))) - .filter(new JarTypeFileSpec()); - }); - bootJar.getMainClass().convention(project.provider(() -> { - String manifestStartClass = (String) bootJar.getManifest().getAttributes().get("Start-Class"); - return (manifestStartClass != null) ? manifestStartClass - : new MainClassConvention(project, bootJar::getClasspath).call(); - })); + bootJar.classpath(classpath); + Provider manifestStartClass = project + .provider(() -> (String) bootJar.getManifest().getAttributes().get("Start-Class")); + bootJar.getMainClass().convention(resolveMainClassName.flatMap((resolver) -> manifestStartClass.isPresent() + ? manifestStartClass : resolveMainClassName.get().readMainClassName())); }); } @@ -172,41 +129,28 @@ final class JavaPluginAction implements PluginApplicationAction { this.singlePublishedArtifact.addCandidate(artifact); } - private void configureBootRunTask(Project project, TaskProvider resolveMainClassName) { + private void configureBootRunTask(Project project) { + FileCollection classpath = javaPluginConvention(project).getSourceSets() + .findByName(SourceSet.MAIN_SOURCE_SET_NAME).getRuntimeClasspath().filter(new JarTypeFileSpec()); + TaskProvider resolveProvider = ResolveMainClassName.registerForTask("bootRun", project, + classpath); project.getTasks().register("bootRun", BootRun.class, (run) -> { run.setDescription("Runs this project as a Spring Boot application."); run.setGroup(ApplicationPlugin.APPLICATION_GROUP); - run.classpath(javaPluginConvention(project).getSourceSets().findByName(SourceSet.MAIN_SOURCE_SET_NAME) - .getRuntimeClasspath().filter(new JarTypeFileSpec())); + run.classpath(classpath); run.getConventionMapping().map("jvmArgs", () -> { if (project.hasProperty("applicationDefaultJvmArgs")) { return project.property("applicationDefaultJvmArgs"); } return Collections.emptyList(); }); - run.dependsOn(resolveMainClassName); - run.getInputs().file(resolveMainClassName.map((task) -> task.getOutputFile())); try { - run.getMainClass().set(resolveMainClassName.flatMap((task) -> readMainClassName(task.getOutputFile()))); + run.getMainClass().convention(resolveProvider.flatMap(ResolveMainClassName::readMainClassName)); } catch (NoSuchMethodError ex) { + run.getInputs().file(resolveProvider.map((task) -> task.getOutputFile())); run.conventionMapping("main", - () -> resolveMainClassName.flatMap((task) -> readMainClassName(task.getOutputFile())).get()); - } - }); - } - - private Provider readMainClassName(RegularFileProperty outputFile) { - return outputFile.map((file) -> { - Path output = file.getAsFile().toPath(); - if (!Files.exists(output)) { - return null; - } - try { - return new String(Files.readAllBytes(output), StandardCharsets.UTF_8); - } - catch (IOException ex) { - throw new RuntimeException("Failed to read main class name from '" + output + "'"); + () -> resolveProvider.flatMap(ResolveMainClassName::readMainClassName).get()); } }); } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/MainClassConvention.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/MainClassConvention.java deleted file mode 100644 index 8d14be4978..0000000000 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/MainClassConvention.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright 2012-2020 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.gradle.plugin; - -import java.io.File; -import java.io.IOException; -import java.lang.reflect.Method; -import java.util.Objects; -import java.util.concurrent.Callable; -import java.util.function.Supplier; - -import org.gradle.api.InvalidUserDataException; -import org.gradle.api.Project; -import org.gradle.api.file.FileCollection; -import org.gradle.api.plugins.JavaApplication; -import org.gradle.api.provider.Property; - -import org.springframework.boot.gradle.dsl.SpringBootExtension; -import org.springframework.boot.loader.tools.MainClassFinder; - -/** - * A {@link Callable} that provides a convention for the project's main class name. - * - * @author Andy Wilkinson - */ -final class MainClassConvention implements Callable { - - private static final String SPRING_BOOT_APPLICATION_CLASS_NAME = "org.springframework.boot.autoconfigure.SpringBootApplication"; - - private final Project project; - - private final Supplier classpathSupplier; - - MainClassConvention(Project project, Supplier classpathSupplier) { - this.project = project; - this.classpathSupplier = classpathSupplier; - } - - @Override - public String call() throws Exception { - SpringBootExtension springBootExtension = this.project.getExtensions().findByType(SpringBootExtension.class); - if (springBootExtension != null) { - String mainClass = springBootExtension.getMainClass().getOrNull(); - if (mainClass != null) { - return mainClass; - } - } - String javaApplicationMainClass = getJavaApplicationMainClass(); - return (javaApplicationMainClass != null) ? javaApplicationMainClass : resolveMainClass(); - } - - @SuppressWarnings({ "unchecked", "deprecation" }) - private String getJavaApplicationMainClass() { - JavaApplication javaApplication = this.project.getConvention().findByType(JavaApplication.class); - if (javaApplication == null) { - return null; - } - Method getMainClass = findMethod(JavaApplication.class, "getMainClass"); - if (getMainClass != null) { - try { - Property mainClass = (Property) getMainClass.invoke(javaApplication); - return mainClass.getOrElse(null); - } - catch (Exception ex) { - // Continue - } - } - return javaApplication.getMainClassName(); - } - - private static Method findMethod(Class type, String name) { - for (Method candidate : type.getMethods()) { - if (candidate.getName().equals(name)) { - return candidate; - } - } - return null; - } - - private String resolveMainClass() { - return this.classpathSupplier.get().filter(File::isDirectory).getFiles().stream().map(this::findMainClass) - .filter(Objects::nonNull).findFirst().orElseThrow(() -> new InvalidUserDataException( - "Main class name has not been configured and it could not be resolved")); - } - - private String findMainClass(File file) { - try { - return MainClassFinder.findSingleMainClass(file, SPRING_BOOT_APPLICATION_CLASS_NAME); - } - catch (IOException ex) { - return null; - } - } - -} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/ResolveMainClassName.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/ResolveMainClassName.java index e32fd195b4..2a0f489946 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/ResolveMainClassName.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/ResolveMainClassName.java @@ -20,21 +20,29 @@ import java.io.File; 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.util.Objects; import org.gradle.api.DefaultTask; import org.gradle.api.InvalidUserDataException; +import org.gradle.api.Project; import org.gradle.api.Task; import org.gradle.api.file.FileCollection; import org.gradle.api.file.RegularFileProperty; +import org.gradle.api.plugins.BasePlugin; +import org.gradle.api.plugins.Convention; +import org.gradle.api.plugins.JavaApplication; import org.gradle.api.provider.Property; +import org.gradle.api.provider.Provider; import org.gradle.api.tasks.Classpath; import org.gradle.api.tasks.Input; import org.gradle.api.tasks.Optional; import org.gradle.api.tasks.OutputFile; import org.gradle.api.tasks.TaskAction; +import org.gradle.api.tasks.TaskProvider; +import org.springframework.boot.gradle.dsl.SpringBootExtension; import org.springframework.boot.loader.tools.MainClassFinder; /** @@ -102,11 +110,11 @@ public class ResolveMainClassName extends DefaultTask { @TaskAction void resolveAndStoreMainClassName() throws IOException { - String mainClassName = resolveMainClassName(); File outputFile = this.outputFile.getAsFile().get(); outputFile.getParentFile().mkdirs(); - Files.write(this.outputFile.get().getAsFile().toPath(), mainClassName.getBytes(StandardCharsets.UTF_8), - StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); + String mainClassName = resolveMainClassName(); + Files.write(outputFile.toPath(), mainClassName.getBytes(StandardCharsets.UTF_8), StandardOpenOption.WRITE, + StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); } private String resolveMainClassName() { @@ -115,8 +123,7 @@ public class ResolveMainClassName extends DefaultTask { return configuredMainClass; } return getClasspath().filter(File::isDirectory).getFiles().stream().map(this::findMainClass) - .filter(Objects::nonNull).findFirst().orElseThrow(() -> new InvalidUserDataException( - "Main class name has not been configured and it could not be resolved")); + .filter(Objects::nonNull).findFirst().orElse(""); } private String findMainClass(File file) { @@ -128,4 +135,58 @@ public class ResolveMainClassName extends DefaultTask { } } + Provider readMainClassName() { + return this.outputFile.map((file) -> { + if (file.getAsFile().length() == 0) { + throw new InvalidUserDataException( + "Main class name has not been configured and it could not be resolved"); + } + Path output = file.getAsFile().toPath(); + try { + return new String(Files.readAllBytes(output), StandardCharsets.UTF_8); + } + catch (IOException ex) { + throw new RuntimeException("Failed to read main class name from '" + output + "'"); + } + }); + } + + static TaskProvider registerForTask(String taskName, Project project, + FileCollection classpath) { + TaskProvider resolveMainClassNameProvider = project.getTasks() + .register(taskName + "MainClassName", ResolveMainClassName.class, (resolveMainClassName) -> { + Convention convention = project.getConvention(); + resolveMainClassName.setDescription( + "Resolves the name of the application's main class for the " + taskName + " task."); + resolveMainClassName.setGroup(BasePlugin.BUILD_GROUP); + resolveMainClassName.setClasspath(classpath); + resolveMainClassName.getConfiguredMainClassName().convention(project.provider(() -> { + String javaApplicationMainClass = getJavaApplicationMainClass(convention); + if (javaApplicationMainClass != null) { + return javaApplicationMainClass; + } + SpringBootExtension springBootExtension = project.getExtensions() + .findByType(SpringBootExtension.class); + return springBootExtension.getMainClass().getOrNull(); + })); + resolveMainClassName.getOutputFile() + .set(project.getLayout().getBuildDirectory().file(taskName + "MainClassName")); + }); + return resolveMainClassNameProvider; + } + + @SuppressWarnings("deprecation") + private static String getJavaApplicationMainClass(Convention convention) { + JavaApplication javaApplication = convention.findByType(JavaApplication.class); + if (javaApplication == null) { + return null; + } + try { + return javaApplication.getMainClass().getOrNull(); + } + catch (NoSuchMethodError ex) { + return javaApplication.getMainClassName(); + } + } + } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/WarPluginAction.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/WarPluginAction.java index b945d4c9a0..310322cd48 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/WarPluginAction.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/WarPluginAction.java @@ -25,6 +25,9 @@ import org.gradle.api.file.FileCollection; import org.gradle.api.internal.artifacts.dsl.LazyPublishArtifact; import org.gradle.api.plugins.BasePlugin; import org.gradle.api.plugins.WarPlugin; +import org.gradle.api.provider.Provider; +import org.gradle.api.tasks.SourceSet; +import org.gradle.api.tasks.SourceSetContainer; import org.gradle.api.tasks.TaskProvider; import org.springframework.boot.gradle.tasks.bundling.BootWar; @@ -60,23 +63,31 @@ class WarPluginAction implements PluginApplicationAction { } private TaskProvider configureBootWarTask(Project project) { - return project.getTasks().register(SpringBootPlugin.BOOT_WAR_TASK_NAME, BootWar.class, (bootWar) -> { - bootWar.setGroup(BasePlugin.BUILD_GROUP); - bootWar.setDescription("Assembles an executable war archive containing webapp" - + " content, and the main classes and their dependencies."); - bootWar.providedClasspath(providedRuntimeConfiguration(project)); - Configuration developmentOnly = project.getConfigurations() - .getByName(SpringBootPlugin.DEVELOPMENT_ONLY_CONFIGURATION_NAME); - Configuration productionRuntimeClasspath = project.getConfigurations() - .getByName(SpringBootPlugin.PRODUCTION_RUNTIME_CLASSPATH_NAME); - bootWar.setClasspath(bootWar.getClasspath().minus((developmentOnly.minus(productionRuntimeClasspath))) - .filter(new JarTypeFileSpec())); - bootWar.getMainClass().convention(project.provider(() -> { - String manifestStartClass = (String) bootWar.getManifest().getAttributes().get("Start-Class"); - return (manifestStartClass != null) ? manifestStartClass - : new MainClassConvention(project, bootWar::getClasspath).call(); - })); - }); + Configuration developmentOnly = project.getConfigurations() + .getByName(SpringBootPlugin.DEVELOPMENT_ONLY_CONFIGURATION_NAME); + Configuration productionRuntimeClasspath = project.getConfigurations() + .getByName(SpringBootPlugin.PRODUCTION_RUNTIME_CLASSPATH_NAME); + FileCollection classpath = project.getConvention().getByType(SourceSetContainer.class) + .getByName(SourceSet.MAIN_SOURCE_SET_NAME).getRuntimeClasspath() + .minus(providedRuntimeConfiguration(project)).minus((developmentOnly.minus(productionRuntimeClasspath))) + .filter(new JarTypeFileSpec()); + TaskProvider resolveMainClassName = ResolveMainClassName + .registerForTask(SpringBootPlugin.BOOT_WAR_TASK_NAME, project, classpath); + TaskProvider bootWarProvider = project.getTasks().register(SpringBootPlugin.BOOT_WAR_TASK_NAME, + BootWar.class, (bootWar) -> { + bootWar.setGroup(BasePlugin.BUILD_GROUP); + bootWar.setDescription("Assembles an executable war archive containing webapp" + + " content, and the main classes and their dependencies."); + bootWar.providedClasspath(providedRuntimeConfiguration(project)); + bootWar.setClasspath(classpath); + Provider manifestStartClass = project + .provider(() -> (String) bootWar.getManifest().getAttributes().get("Start-Class")); + bootWar.getMainClass() + .convention(resolveMainClassName.flatMap((resolver) -> manifestStartClass.isPresent() + ? manifestStartClass : resolveMainClassName.get().readMainClassName())); + }); + bootWarProvider.map((bootWar) -> bootWar.getClasspath()); + return bootWarProvider; } private FileCollection providedRuntimeConfiguration(Project project) { diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/junit/GradleCompatibilityExtension.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/junit/GradleCompatibilityExtension.java index 18fe04cdf7..d2d360730d 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/junit/GradleCompatibilityExtension.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/junit/GradleCompatibilityExtension.java @@ -66,8 +66,7 @@ final class GradleCompatibilityExtension implements TestTemplateInvocationContex boolean configurationCache = AnnotationUtils .findAnnotation(context.getRequiredTestClass(), GradleCompatibility.class).get() .configurationCache(); - if (configurationCache - && GradleVersion.version(version).compareTo(GradleVersion.version("6.7-rc-1")) >= 0) { + if (configurationCache && GradleVersion.version(version).compareTo(GradleVersion.version("6.7")) >= 0) { invocationContexts.add(new GradleVersionTestTemplateInvocationContext(version, true)); } return invocationContexts.stream(); diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/plugin/MainClassConventionTests.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/plugin/MainClassConventionTests.java deleted file mode 100644 index c67c920638..0000000000 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/plugin/MainClassConventionTests.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright 2012-2020 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.gradle.plugin; - -import java.io.File; -import java.io.IOException; - -import org.gradle.api.Project; -import org.gradle.api.plugins.ApplicationPlugin; -import org.gradle.api.plugins.JavaApplication; -import org.gradle.testfixtures.ProjectBuilder; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.io.TempDir; - -import org.springframework.boot.gradle.dsl.SpringBootExtension; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Integration tests for {@link MainClassConvention}. - * - * @author Andy Wilkinson - */ -class MainClassConventionTests { - - @TempDir - File temp; - - private Project project; - - private MainClassConvention convention; - - @BeforeEach - void createConvention() throws IOException { - this.project = ProjectBuilder.builder().withProjectDir(this.temp).build(); - this.convention = new MainClassConvention(this.project, () -> null); - } - - @Test - void javaApplicationExtensionMainClassNameIsUsed() throws Exception { - this.project.getPlugins().apply(ApplicationPlugin.class); - JavaApplication extension = this.project.getExtensions().findByType(JavaApplication.class); - extension.getMainClass().set("com.example.MainClass"); - assertThat(this.convention.call()).isEqualTo("com.example.MainClass"); - } - - @Test - void springBootExtensionMainClassNameIsUsed() throws Exception { - SpringBootExtension extension = this.project.getExtensions().create("springBoot", SpringBootExtension.class, - this.project); - extension.getMainClass().set("com.example.MainClass"); - assertThat(this.convention.call()).isEqualTo("com.example.MainClass"); - } - - @Test - void springBootExtensionMainClassNameIsUsedInPreferenceToJavaApplicationExtensionMainClassName() throws Exception { - this.project.getPlugins().apply(ApplicationPlugin.class); - JavaApplication javaApplication = this.project.getExtensions().findByType(JavaApplication.class); - javaApplication.getMainClass().set("com.example.JavaApplicationMainClass"); - SpringBootExtension extension = this.project.getExtensions().create("springBoot", SpringBootExtension.class, - this.project); - extension.getMainClass().set("com.example.SpringBootExtensionMainClass"); - assertThat(this.convention.call()).isEqualTo("com.example.SpringBootExtensionMainClass"); - } - -} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/AbstractBootArchiveIntegrationTests.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/AbstractBootArchiveIntegrationTests.java index c4e2476281..d2f5b06c39 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/AbstractBootArchiveIntegrationTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/AbstractBootArchiveIntegrationTests.java @@ -34,6 +34,7 @@ import org.junit.jupiter.api.TestTemplate; import org.springframework.boot.gradle.testkit.GradleBuild; import org.springframework.boot.loader.tools.FileUtils; +import org.springframework.util.FileSystemUtils; import static org.assertj.core.api.Assertions.assertThat; @@ -188,6 +189,27 @@ abstract class AbstractBootArchiveIntegrationTests { } } + @TestTemplate + void startClassIsSetByResolvingTheMainClass() throws IOException { + copyMainClassApplication(); + assertThat(this.gradleBuild.build(this.taskName).task(":" + this.taskName).getOutcome()) + .isEqualTo(TaskOutcome.SUCCESS); + try (JarFile jarFile = new JarFile(new File(this.gradleBuild.getProjectDir(), "build/libs").listFiles()[0])) { + Attributes mainAttributes = jarFile.getManifest().getMainAttributes(); + assertThat(mainAttributes.getValue("Start-Class")).isEqualTo("com.example.main.CustomMainClass"); + } + } + + private void copyMainClassApplication() throws IOException { + copyApplication("main"); + } + + private void copyApplication(String name) throws IOException { + File output = new File(this.gradleBuild.getProjectDir(), "src/main/java/com/example/" + name); + output.mkdirs(); + FileSystemUtils.copyRecursively(new File("src/test/java/com/example/" + name), output); + } + private void createStandardJar(File location) throws IOException { createJar(location, (attributes) -> { }); diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-startClassIsSetByResolvingTheMainClass.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-startClassIsSetByResolvingTheMainClass.gradle new file mode 100644 index 0000000000..617daecbe6 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-startClassIsSetByResolvingTheMainClass.gradle @@ -0,0 +1,5 @@ +plugins { + id 'java' + id 'application' + id 'org.springframework.boot' version '{version}' +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-startClassIsSetByResolvingTheMainClass.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-startClassIsSetByResolvingTheMainClass.gradle new file mode 100644 index 0000000000..0ee67eb10d --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-startClassIsSetByResolvingTheMainClass.gradle @@ -0,0 +1,5 @@ +plugins { + id 'war' + id 'application' + id 'org.springframework.boot' version '{version}' +} From 806464edd1747a17a776899606dd243af6060310 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Fri, 23 Oct 2020 12:09:01 +0100 Subject: [PATCH 8/9] Update BootBuildImage to support Gradle's configuration cache See gh-22922 --- .../boot/gradle/tasks/bundling/BootBuildImage.java | 14 ++++++++++---- .../bundling/BootBuildImageIntegrationTests.java | 2 +- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootBuildImage.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootBuildImage.java index f9178ff96b..0259b14507 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootBuildImage.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootBuildImage.java @@ -59,6 +59,10 @@ public class BootBuildImage extends DefaultTask { private static final String BUILDPACK_JVM_VERSION_KEY = "BP_JVM_VERSION"; + private final String projectName; + + private final Property projectVersion; + private RegularFileProperty jar; private Property targetJavaVersion; @@ -84,6 +88,9 @@ public class BootBuildImage extends DefaultTask { public BootBuildImage() { this.jar = getProject().getObjects().fileProperty(); this.targetJavaVersion = getProject().getObjects().property(JavaVersion.class); + this.projectName = getProject().getName(); + this.projectVersion = getProject().getObjects().property(String.class); + this.projectVersion.set(getProject().provider(() -> getProject().getVersion().toString())); } /** @@ -318,12 +325,11 @@ public class BootBuildImage extends DefaultTask { if (StringUtils.hasText(this.imageName)) { return ImageReference.of(this.imageName); } - ImageName imageName = ImageName.of(getProject().getName()); - String version = getProject().getVersion().toString(); - if ("unspecified".equals(version)) { + ImageName imageName = ImageName.of(this.projectName); + if ("unspecified".equals(this.projectVersion.get())) { return ImageReference.of(imageName); } - return ImageReference.of(imageName, version); + return ImageReference.of(imageName, this.projectVersion.get()); } private BuildRequest customize(BuildRequest request) { diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests.java index f4f1888214..14e2b8e4c4 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests.java @@ -46,7 +46,7 @@ import static org.assertj.core.api.Assertions.assertThat; * @author Andy Wilkinson * @author Scott Frederick */ -@GradleCompatibility +@GradleCompatibility(configurationCache = true) @DisabledIfDockerUnavailable class BootBuildImageIntegrationTests { From c409d50c145e135f5f0f6323b3855fd0bd5c758f Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Fri, 23 Oct 2020 12:32:41 +0100 Subject: [PATCH 9/9] Document support for Gradle's configuration cache See gh-22922 --- .../spring-boot-gradle-plugin/src/docs/asciidoc/index.adoc | 1 + 1 file changed, 1 insertion(+) diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/asciidoc/index.adoc b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/asciidoc/index.adoc index a34f42ffeb..cf15a83513 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/asciidoc/index.adoc +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/docs/asciidoc/index.adoc @@ -43,6 +43,7 @@ The Spring Boot Gradle Plugin provides Spring Boot support in https://gradle.org It allows you to package executable jar or war archives, run Spring Boot applications, and use the dependency management provided by `spring-boot-dependencies`. Spring Boot's Gradle plugin requires Gradle 6 (6.3 or later). Gradle 5.6 is also supported but this support is deprecated and will be removed in a future release. +Gradle's {gradle-userguide}/configuration_cache.html[configuration cache] is supported when using Gradle 6.7 or later. In addition to this user guide, {api-documentation}[API documentation] is also available.