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");