From 379ba0dc002588fe1a50c9c53c896eb88c5e2811 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Thu, 24 Oct 2019 10:35:56 +0100 Subject: [PATCH] Support Gradle 6.0 Previously, our Gradle plugin was not tested against Gradle 6.0, a number of deprecation warnings were output when using the plugin with Gradle 6, and some functionality related to the application plugin did not work as expected. This commit tests the plugin against Gradle 6. It also avoids calling deprecated APIs. The plugin is compatibile against Gradle 4.10 where the deprecated APIs' replacements are not available so reflection is used to call the replcaements. Lastly, the way in which the base name of the boot distribution that is created when the application plugin is applied has been modified to ensure that it is effective when using Gradle 6. Closes gh-18663 --- .../src/main/asciidoc/getting-started.adoc | 4 +- .../src/main/asciidoc/index.adoc | 2 +- .../src/main/asciidoc/publishing.adoc | 2 +- .../src/main/asciidoc/running.adoc | 2 +- .../application-plugin-main-class-name.gradle | 4 +- .../boot/gradle/dsl/SpringBootExtension.java | 26 ++++++++++++- .../plugin/ApplicationPluginAction.java | 39 +++++++++++++++++-- .../gradle/plugin/MainClassConvention.java | 9 ++--- .../tasks/bundling/BootArchiveSupport.java | 26 ++++++++++++- .../bundling/LaunchScriptConfiguration.java | 32 ++++++++++++--- .../junit/GradleCompatibilityExtension.java | 2 +- .../plugin/MainClassConventionTests.java | 15 ++++--- 12 files changed, 134 insertions(+), 29 deletions(-) diff --git a/spring-boot-project/spring-boot-docs/src/main/asciidoc/getting-started.adoc b/spring-boot-project/spring-boot-docs/src/main/asciidoc/getting-started.adoc index dc1b01c736b6..8a7b2981de25 100644 --- a/spring-boot-project/spring-boot-docs/src/main/asciidoc/getting-started.adoc +++ b/spring-boot-project/spring-boot-docs/src/main/asciidoc/getting-started.adoc @@ -41,7 +41,7 @@ Explicit build support is provided for the following build tools: | 3.3+ | Gradle -| 5.x (4.10 is also supported but in a deprecated form) +| 5.x and 6.x (4.10 is also supported but in a deprecated form) |=== @@ -195,7 +195,7 @@ In those cases, see <> [[getting-started-gradle-installation]] ==== Gradle Installation -Spring Boot is compatible with 5.x. +Spring Boot is compatible with 5.x and 6.x. 4.10 is also supported but this support is deprecated and will be removed in a future release. If you do not already have Gradle installed, you can follow the instructions at https://gradle.org. diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/asciidoc/index.adoc b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/asciidoc/index.adoc index 7f0b02c34fcb..c13bb7fdc08d 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/asciidoc/index.adoc +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/asciidoc/index.adoc @@ -38,7 +38,7 @@ Andy Wilkinson The Spring Boot Gradle Plugin provides Spring Boot support in https://gradle.org[Gradle]. 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 5.x (4.10 is also supported but this support is deprecated and will be removed in a future release). +Spring Boot's Gradle plugin requires Gradle 5.x or 6.x (4.10 is also supported but this support is deprecated and will be removed in a future release). In addition to this user guide, {api-documentation}[API documentation] is also available. diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/asciidoc/publishing.adoc b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/asciidoc/publishing.adoc index 0b78cf3f1802..467aee1a7964 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/asciidoc/publishing.adoc +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/asciidoc/publishing.adoc @@ -49,4 +49,4 @@ include::../gradle/publishing/maven-publish.gradle.kts[tags=publishing] When the {application-plugin}[`application` plugin] is applied a distribution named `boot` is created. This distribution contains the archive produced by the `bootJar` or `bootWar` task and scripts to launch it on Unix-like platforms and Windows. Zip and tar distributions can be built by the `bootDistZip` and `bootDistTar` tasks respectively. -To use the `application` plugin, its `mainClassName` project property must be configured with the name of your application's main class. +To use the `application` plugin, its `mainClassName` property must be configured with the name of your application's main class. diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/asciidoc/running.adoc b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/asciidoc/running.adoc index 868b901ceeb3..015435723493 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/asciidoc/running.adoc +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/asciidoc/running.adoc @@ -60,7 +60,7 @@ include::../gradle/running/boot-run-disable-optimized-launch.gradle.kts[tags=lau ---- -If the {application-plugin}[`application` plugin] has been applied, its `mainClassName` project property must be configured and can be used for the same purpose: +If the {application-plugin}[`application` plugin] has been applied, its `mainClassName` property must be configured and can be used for the same purpose: [source,groovy,indent=0,subs="verbatim,attributes",role="primary"] .Groovy diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/gradle/running/application-plugin-main-class-name.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/gradle/running/application-plugin-main-class-name.gradle index 894cb9b75437..cb8473a997c2 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/gradle/running/application-plugin-main-class-name.gradle +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/gradle/running/application-plugin-main-class-name.gradle @@ -5,7 +5,9 @@ plugins { } // tag::main-class[] -mainClassName = 'com.example.ExampleApplication' +application { + mainClassName = 'com.example.ExampleApplication' +} // end::main-class[] task configuredMainClass { diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/dsl/SpringBootExtension.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/dsl/SpringBootExtension.java index 2fac0c020ef8..adeb6f544958 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/dsl/SpringBootExtension.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/dsl/SpringBootExtension.java @@ -17,6 +17,7 @@ package org.springframework.boot.gradle.dsl; import java.io.File; +import java.lang.reflect.Method; import org.gradle.api.Action; import org.gradle.api.Project; @@ -24,6 +25,7 @@ import org.gradle.api.plugins.JavaPlugin; import org.gradle.api.plugins.JavaPluginConvention; import org.gradle.api.tasks.SourceSet; +import org.gradle.api.tasks.bundling.AbstractArchiveTask; import org.gradle.jvm.tasks.Jar; import org.springframework.boot.gradle.tasks.buildinfo.BuildInfo; @@ -115,7 +117,7 @@ private File determineMainSourceSetResourcesOutputDir() { private String determineArtifactBaseName() { Jar artifactTask = findArtifactTask(); - return (artifactTask != null) ? artifactTask.getBaseName() : null; + return (artifactTask != null) ? getArchiveBaseName(artifactTask) : null; } private Jar findArtifactTask() { @@ -126,4 +128,26 @@ private Jar findArtifactTask() { return (Jar) this.project.getTasks().findByName("bootJar"); } + private static String getArchiveBaseName(AbstractArchiveTask task) { + try { + Method method = findMethod(task.getClass(), "getArchiveBaseName"); + if (method != null) { + return (String) method.invoke(task); + } + } + catch (Exception ex) { + // Continue + } + return task.getBaseName(); + } + + private static Method findMethod(Class type, String name) { + for (Method candidate : type.getMethods()) { + if (candidate.getName().equals(name)) { + return candidate; + } + } + return null; + } + } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/ApplicationPluginAction.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/ApplicationPluginAction.java index 4d226179f24b..881dc0c16a85 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/ApplicationPluginAction.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/ApplicationPluginAction.java @@ -20,6 +20,7 @@ import java.io.IOException; import java.io.InputStreamReader; import java.io.StringWriter; +import java.lang.reflect.Method; import java.util.concurrent.Callable; import org.gradle.api.GradleException; @@ -32,6 +33,8 @@ import org.gradle.api.internal.IConventionAware; import org.gradle.api.plugins.ApplicationPlugin; import org.gradle.api.plugins.ApplicationPluginConvention; +import org.gradle.api.provider.Property; +import org.gradle.api.provider.Provider; import org.gradle.jvm.application.scripts.TemplateBasedScriptGenerator; import org.springframework.boot.gradle.tasks.application.CreateBootStartScripts; @@ -49,10 +52,7 @@ public void execute(Project project) { .getPlugin(ApplicationPluginConvention.class); DistributionContainer distributions = project.getExtensions().getByType(DistributionContainer.class); Distribution distribution = distributions.create("boot"); - if (distribution instanceof IConventionAware) { - ((IConventionAware) distribution).getConventionMapping().map("baseName", - () -> applicationConvention.getApplicationName() + "-boot"); - } + configureBaseNameConvention(project, applicationConvention, distribution); CreateBootStartScripts bootStartScripts = project.getTasks().create("bootStartScripts", CreateBootStartScripts.class); bootStartScripts @@ -79,6 +79,37 @@ public void execute(Project project) { distribution.getContents().with(binCopySpec); } + @SuppressWarnings("unchecked") + private void configureBaseNameConvention(Project project, ApplicationPluginConvention applicationConvention, + Distribution distribution) { + Method getDistributionBaseName = findMethod(distribution.getClass(), "getDistributionBaseName"); + if (getDistributionBaseName != null) { + try { + Property distributionBaseName = (Property) distribution.getClass() + .getMethod("getDistributionBaseName").invoke(distribution); + distributionBaseName.getClass().getMethod("convention", Provider.class).invoke(distributionBaseName, + project.provider(() -> applicationConvention.getApplicationName() + "-boot")); + return; + } + catch (Exception ex) { + // Continue + } + } + if (distribution instanceof IConventionAware) { + ((IConventionAware) distribution).getConventionMapping().map("baseName", + () -> applicationConvention.getApplicationName() + "-boot"); + } + } + + private static Method findMethod(Class type, String name) { + for (Method candidate : type.getMethods()) { + if (candidate.getName().equals(name)) { + return candidate; + } + } + return null; + } + @Override public Class> getPluginClass() { return ApplicationPlugin.class; 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 index b929b0f0fe00..3861fbf80ea1 100644 --- 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 @@ -25,6 +25,7 @@ import org.gradle.api.InvalidUserDataException; import org.gradle.api.Project; import org.gradle.api.file.FileCollection; +import org.gradle.api.plugins.JavaApplication; import org.springframework.boot.gradle.dsl.SpringBootExtension; import org.springframework.boot.loader.tools.MainClassFinder; @@ -53,11 +54,9 @@ public Object call() throws Exception { if (springBootExtension != null && springBootExtension.getMainClassName() != null) { return springBootExtension.getMainClassName(); } - if (this.project.hasProperty("mainClassName")) { - Object mainClassName = this.project.property("mainClassName"); - if (mainClassName != null) { - return mainClassName; - } + JavaApplication javaApplication = this.project.getConvention().findByType(JavaApplication.class); + if (javaApplication != null && javaApplication.getMainClassName() != null) { + return javaApplication.getMainClassName(); } return resolveMainClass(); } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootArchiveSupport.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootArchiveSupport.java index 330b31439858..ed951bee5a81 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootArchiveSupport.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootArchiveSupport.java @@ -20,6 +20,7 @@ import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; +import java.lang.reflect.Method; import java.util.Collections; import java.util.HashSet; import java.util.Map; @@ -37,6 +38,7 @@ import org.gradle.api.specs.Spec; import org.gradle.api.specs.Specs; import org.gradle.api.tasks.WorkResult; +import org.gradle.api.tasks.bundling.AbstractArchiveTask; import org.gradle.api.tasks.bundling.Jar; import org.gradle.api.tasks.util.PatternSet; @@ -93,7 +95,7 @@ private String determineSpringBootVersion() { } CopyAction createCopyAction(Jar jar) { - CopyAction copyAction = new BootZipCopyAction(jar.getArchivePath(), jar.isPreserveFileTimestamps(), + CopyAction copyAction = new BootZipCopyAction(getOutputLocation(jar), jar.isPreserveFileTimestamps(), isUsingDefaultLoader(jar), this.requiresUnpack.getAsSpec(), this.exclusions.getAsExcludeSpec(), this.launchScript, this.compressionResolver, jar.getMetadataCharset()); if (!jar.isReproducibleFileOrder()) { @@ -102,6 +104,28 @@ CopyAction createCopyAction(Jar jar) { return new ReproducibleOrderingCopyAction(copyAction); } + private static File getOutputLocation(AbstractArchiveTask task) { + try { + Method method = findMethod(task.getClass(), "getArchiveFile"); + if (method != null) { + return (File) method.invoke(task); + } + } + catch (Exception ex) { + // Continue + } + return task.getArchivePath(); + } + + private static Method findMethod(Class type, String name) { + for (Method candidate : type.getMethods()) { + if (candidate.getName().equals(name)) { + return candidate; + } + } + return null; + } + private boolean isUsingDefaultLoader(Jar jar) { return DEFAULT_LAUNCHER_CLASSES.contains(jar.getManifest().getAttributes().get("Main-Class")); } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/LaunchScriptConfiguration.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/LaunchScriptConfiguration.java index c084cecaa504..f2cbd8e1adb9 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/LaunchScriptConfiguration.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/LaunchScriptConfiguration.java @@ -19,6 +19,7 @@ import java.io.File; import java.io.IOException; import java.io.Serializable; +import java.lang.reflect.Method; import java.util.HashMap; import java.util.Map; import java.util.regex.Pattern; @@ -50,11 +51,32 @@ public LaunchScriptConfiguration() { LaunchScriptConfiguration(AbstractArchiveTask archiveTask) { Project project = archiveTask.getProject(); - putIfMissing(this.properties, "initInfoProvides", archiveTask.getBaseName()); - putIfMissing(this.properties, "initInfoShortDescription", removeLineBreaks(project.getDescription()), - archiveTask.getBaseName()); - putIfMissing(this.properties, "initInfoDescription", augmentLineBreaks(project.getDescription()), - archiveTask.getBaseName()); + String baseName = getArchiveBaseName(archiveTask); + putIfMissing(this.properties, "initInfoProvides", baseName); + putIfMissing(this.properties, "initInfoShortDescription", removeLineBreaks(project.getDescription()), baseName); + putIfMissing(this.properties, "initInfoDescription", augmentLineBreaks(project.getDescription()), baseName); + } + + private static String getArchiveBaseName(AbstractArchiveTask task) { + try { + Method method = findMethod(task.getClass(), "getArchiveBaseName"); + if (method != null) { + return (String) method.invoke(task); + } + } + catch (Exception ex) { + // Continue + } + return task.getBaseName(); + } + + private static Method findMethod(Class type, String name) { + for (Method candidate : type.getMethods()) { + if (candidate.getName().equals(name)) { + return candidate; + } + } + return null; } /** 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 30d63b5dda7e..8e128130d84e 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 @@ -39,7 +39,7 @@ public final class GradleCompatibilityExtension implements TestTemplateInvocationContextProvider { private static final List GRADLE_VERSIONS = Arrays.asList("default", "5.0", "5.1.1", "5.2.1", "5.3.1", - "5.4.1", "5.5.1", "5.6.4"); + "5.4.1", "5.5.1", "5.6.4", "6.0"); @Override public Stream provideTestTemplateInvocationContexts(ExtensionContext context) { 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 index 34e6c115af68..815f1a692f6c 100644 --- 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 @@ -20,7 +20,8 @@ import java.io.IOException; import org.gradle.api.Project; -import org.gradle.api.plugins.ExtraPropertiesExtension; +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; @@ -51,9 +52,10 @@ void createConvention() throws IOException { } @Test - void mainClassNameProjectPropertyIsUsed() throws Exception { - this.project.getExtensions().getByType(ExtraPropertiesExtension.class).set("mainClassName", - "com.example.MainClass"); + void javaApplicationExtensionMainClassNameIsUsed() throws Exception { + this.project.getPlugins().apply(ApplicationPlugin.class); + JavaApplication extension = this.project.getExtensions().findByType(JavaApplication.class); + extension.setMainClassName("com.example.MainClass"); assertThat(this.convention.call()).isEqualTo("com.example.MainClass"); } @@ -67,8 +69,9 @@ void springBootExtensionMainClassNameIsUsed() throws Exception { @Test void springBootExtensionMainClassNameIsUsedInPreferenceToMainClassNameProjectProperty() throws Exception { - this.project.getExtensions().getByType(ExtraPropertiesExtension.class).set("mainClassName", - "com.example.ProjectPropertyMainClass"); + this.project.getPlugins().apply(ApplicationPlugin.class); + JavaApplication javaApplication = this.project.getExtensions().findByType(JavaApplication.class); + javaApplication.setMainClassName("com.example.JavaApplicationMainClass"); SpringBootExtension extension = this.project.getExtensions().create("springBoot", SpringBootExtension.class, this.project); extension.setMainClassName("com.example.SpringBootExtensionMainClass");