Commit 379ba0dc authored by Andy Wilkinson's avatar Andy Wilkinson

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
parent f9dc8155
...@@ -41,7 +41,7 @@ Explicit build support is provided for the following build tools: ...@@ -41,7 +41,7 @@ Explicit build support is provided for the following build tools:
| 3.3+ | 3.3+
| Gradle | 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 <<using-spring-boot.adoc#using-boot-maven-without-a-parent>> ...@@ -195,7 +195,7 @@ In those cases, see <<using-spring-boot.adoc#using-boot-maven-without-a-parent>>
[[getting-started-gradle-installation]] [[getting-started-gradle-installation]]
==== 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. 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. If you do not already have Gradle installed, you can follow the instructions at https://gradle.org.
......
...@@ -38,7 +38,7 @@ Andy Wilkinson ...@@ -38,7 +38,7 @@ Andy Wilkinson
The Spring Boot Gradle Plugin provides Spring Boot support in https://gradle.org[Gradle]. 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`. 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. In addition to this user guide, {api-documentation}[API documentation] is also available.
......
...@@ -49,4 +49,4 @@ include::../gradle/publishing/maven-publish.gradle.kts[tags=publishing] ...@@ -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. 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. 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. 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.
...@@ -60,7 +60,7 @@ include::../gradle/running/boot-run-disable-optimized-launch.gradle.kts[tags=lau ...@@ -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"] [source,groovy,indent=0,subs="verbatim,attributes",role="primary"]
.Groovy .Groovy
......
...@@ -5,7 +5,9 @@ plugins { ...@@ -5,7 +5,9 @@ plugins {
} }
// tag::main-class[] // tag::main-class[]
mainClassName = 'com.example.ExampleApplication' application {
mainClassName = 'com.example.ExampleApplication'
}
// end::main-class[] // end::main-class[]
task configuredMainClass { task configuredMainClass {
......
...@@ -17,6 +17,7 @@ ...@@ -17,6 +17,7 @@
package org.springframework.boot.gradle.dsl; package org.springframework.boot.gradle.dsl;
import java.io.File; import java.io.File;
import java.lang.reflect.Method;
import org.gradle.api.Action; import org.gradle.api.Action;
import org.gradle.api.Project; import org.gradle.api.Project;
...@@ -24,6 +25,7 @@ import org.gradle.api.plugins.BasePlugin; ...@@ -24,6 +25,7 @@ import org.gradle.api.plugins.BasePlugin;
import org.gradle.api.plugins.JavaPlugin; import org.gradle.api.plugins.JavaPlugin;
import org.gradle.api.plugins.JavaPluginConvention; import org.gradle.api.plugins.JavaPluginConvention;
import org.gradle.api.tasks.SourceSet; import org.gradle.api.tasks.SourceSet;
import org.gradle.api.tasks.bundling.AbstractArchiveTask;
import org.gradle.jvm.tasks.Jar; import org.gradle.jvm.tasks.Jar;
import org.springframework.boot.gradle.tasks.buildinfo.BuildInfo; import org.springframework.boot.gradle.tasks.buildinfo.BuildInfo;
...@@ -115,7 +117,7 @@ public class SpringBootExtension { ...@@ -115,7 +117,7 @@ public class SpringBootExtension {
private String determineArtifactBaseName() { private String determineArtifactBaseName() {
Jar artifactTask = findArtifactTask(); Jar artifactTask = findArtifactTask();
return (artifactTask != null) ? artifactTask.getBaseName() : null; return (artifactTask != null) ? getArchiveBaseName(artifactTask) : null;
} }
private Jar findArtifactTask() { private Jar findArtifactTask() {
...@@ -126,4 +128,26 @@ public class SpringBootExtension { ...@@ -126,4 +128,26 @@ public class SpringBootExtension {
return (Jar) this.project.getTasks().findByName("bootJar"); 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;
}
} }
...@@ -20,6 +20,7 @@ import java.io.File; ...@@ -20,6 +20,7 @@ import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.io.StringWriter; import java.io.StringWriter;
import java.lang.reflect.Method;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
import org.gradle.api.GradleException; import org.gradle.api.GradleException;
...@@ -32,6 +33,8 @@ import org.gradle.api.file.FileCollection; ...@@ -32,6 +33,8 @@ import org.gradle.api.file.FileCollection;
import org.gradle.api.internal.IConventionAware; import org.gradle.api.internal.IConventionAware;
import org.gradle.api.plugins.ApplicationPlugin; import org.gradle.api.plugins.ApplicationPlugin;
import org.gradle.api.plugins.ApplicationPluginConvention; 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.gradle.jvm.application.scripts.TemplateBasedScriptGenerator;
import org.springframework.boot.gradle.tasks.application.CreateBootStartScripts; import org.springframework.boot.gradle.tasks.application.CreateBootStartScripts;
...@@ -49,10 +52,7 @@ final class ApplicationPluginAction implements PluginApplicationAction { ...@@ -49,10 +52,7 @@ final class ApplicationPluginAction implements PluginApplicationAction {
.getPlugin(ApplicationPluginConvention.class); .getPlugin(ApplicationPluginConvention.class);
DistributionContainer distributions = project.getExtensions().getByType(DistributionContainer.class); DistributionContainer distributions = project.getExtensions().getByType(DistributionContainer.class);
Distribution distribution = distributions.create("boot"); Distribution distribution = distributions.create("boot");
if (distribution instanceof IConventionAware) { configureBaseNameConvention(project, applicationConvention, distribution);
((IConventionAware) distribution).getConventionMapping().map("baseName",
() -> applicationConvention.getApplicationName() + "-boot");
}
CreateBootStartScripts bootStartScripts = project.getTasks().create("bootStartScripts", CreateBootStartScripts bootStartScripts = project.getTasks().create("bootStartScripts",
CreateBootStartScripts.class); CreateBootStartScripts.class);
bootStartScripts bootStartScripts
...@@ -79,6 +79,37 @@ final class ApplicationPluginAction implements PluginApplicationAction { ...@@ -79,6 +79,37 @@ final class ApplicationPluginAction implements PluginApplicationAction {
distribution.getContents().with(binCopySpec); 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<String> distributionBaseName = (Property<String>) 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 @Override
public Class<? extends Plugin<Project>> getPluginClass() { public Class<? extends Plugin<Project>> getPluginClass() {
return ApplicationPlugin.class; return ApplicationPlugin.class;
......
...@@ -25,6 +25,7 @@ import java.util.function.Supplier; ...@@ -25,6 +25,7 @@ import java.util.function.Supplier;
import org.gradle.api.InvalidUserDataException; import org.gradle.api.InvalidUserDataException;
import org.gradle.api.Project; import org.gradle.api.Project;
import org.gradle.api.file.FileCollection; import org.gradle.api.file.FileCollection;
import org.gradle.api.plugins.JavaApplication;
import org.springframework.boot.gradle.dsl.SpringBootExtension; import org.springframework.boot.gradle.dsl.SpringBootExtension;
import org.springframework.boot.loader.tools.MainClassFinder; import org.springframework.boot.loader.tools.MainClassFinder;
...@@ -53,11 +54,9 @@ final class MainClassConvention implements Callable<Object> { ...@@ -53,11 +54,9 @@ final class MainClassConvention implements Callable<Object> {
if (springBootExtension != null && springBootExtension.getMainClassName() != null) { if (springBootExtension != null && springBootExtension.getMainClassName() != null) {
return springBootExtension.getMainClassName(); return springBootExtension.getMainClassName();
} }
if (this.project.hasProperty("mainClassName")) { JavaApplication javaApplication = this.project.getConvention().findByType(JavaApplication.class);
Object mainClassName = this.project.property("mainClassName"); if (javaApplication != null && javaApplication.getMainClassName() != null) {
if (mainClassName != null) { return javaApplication.getMainClassName();
return mainClassName;
}
} }
return resolveMainClass(); return resolveMainClass();
} }
......
...@@ -20,6 +20,7 @@ import java.io.File; ...@@ -20,6 +20,7 @@ import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.Map; import java.util.Map;
...@@ -37,6 +38,7 @@ import org.gradle.api.java.archives.Attributes; ...@@ -37,6 +38,7 @@ import org.gradle.api.java.archives.Attributes;
import org.gradle.api.specs.Spec; import org.gradle.api.specs.Spec;
import org.gradle.api.specs.Specs; import org.gradle.api.specs.Specs;
import org.gradle.api.tasks.WorkResult; 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.bundling.Jar;
import org.gradle.api.tasks.util.PatternSet; import org.gradle.api.tasks.util.PatternSet;
...@@ -93,7 +95,7 @@ class BootArchiveSupport { ...@@ -93,7 +95,7 @@ class BootArchiveSupport {
} }
CopyAction createCopyAction(Jar jar) { 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(), isUsingDefaultLoader(jar), this.requiresUnpack.getAsSpec(), this.exclusions.getAsExcludeSpec(),
this.launchScript, this.compressionResolver, jar.getMetadataCharset()); this.launchScript, this.compressionResolver, jar.getMetadataCharset());
if (!jar.isReproducibleFileOrder()) { if (!jar.isReproducibleFileOrder()) {
...@@ -102,6 +104,28 @@ class BootArchiveSupport { ...@@ -102,6 +104,28 @@ class BootArchiveSupport {
return new ReproducibleOrderingCopyAction(copyAction); 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) { private boolean isUsingDefaultLoader(Jar jar) {
return DEFAULT_LAUNCHER_CLASSES.contains(jar.getManifest().getAttributes().get("Main-Class")); return DEFAULT_LAUNCHER_CLASSES.contains(jar.getManifest().getAttributes().get("Main-Class"));
} }
......
...@@ -19,6 +19,7 @@ package org.springframework.boot.gradle.tasks.bundling; ...@@ -19,6 +19,7 @@ package org.springframework.boot.gradle.tasks.bundling;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.Serializable; import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.regex.Pattern; import java.util.regex.Pattern;
...@@ -50,11 +51,32 @@ public class LaunchScriptConfiguration implements Serializable { ...@@ -50,11 +51,32 @@ public class LaunchScriptConfiguration implements Serializable {
LaunchScriptConfiguration(AbstractArchiveTask archiveTask) { LaunchScriptConfiguration(AbstractArchiveTask archiveTask) {
Project project = archiveTask.getProject(); Project project = archiveTask.getProject();
putIfMissing(this.properties, "initInfoProvides", archiveTask.getBaseName()); String baseName = getArchiveBaseName(archiveTask);
putIfMissing(this.properties, "initInfoShortDescription", removeLineBreaks(project.getDescription()), putIfMissing(this.properties, "initInfoProvides", baseName);
archiveTask.getBaseName()); putIfMissing(this.properties, "initInfoShortDescription", removeLineBreaks(project.getDescription()), baseName);
putIfMissing(this.properties, "initInfoDescription", augmentLineBreaks(project.getDescription()), putIfMissing(this.properties, "initInfoDescription", augmentLineBreaks(project.getDescription()), baseName);
archiveTask.getBaseName()); }
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;
} }
/** /**
......
...@@ -39,7 +39,7 @@ import org.springframework.boot.gradle.testkit.GradleBuildExtension; ...@@ -39,7 +39,7 @@ import org.springframework.boot.gradle.testkit.GradleBuildExtension;
public final class GradleCompatibilityExtension implements TestTemplateInvocationContextProvider { public final class GradleCompatibilityExtension implements TestTemplateInvocationContextProvider {
private static final List<String> GRADLE_VERSIONS = Arrays.asList("default", "5.0", "5.1.1", "5.2.1", "5.3.1", private static final List<String> 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 @Override
public Stream<TestTemplateInvocationContext> provideTestTemplateInvocationContexts(ExtensionContext context) { public Stream<TestTemplateInvocationContext> provideTestTemplateInvocationContexts(ExtensionContext context) {
......
...@@ -20,7 +20,8 @@ import java.io.File; ...@@ -20,7 +20,8 @@ import java.io.File;
import java.io.IOException; import java.io.IOException;
import org.gradle.api.Project; 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.gradle.testfixtures.ProjectBuilder;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
...@@ -51,9 +52,10 @@ class MainClassConventionTests { ...@@ -51,9 +52,10 @@ class MainClassConventionTests {
} }
@Test @Test
void mainClassNameProjectPropertyIsUsed() throws Exception { void javaApplicationExtensionMainClassNameIsUsed() throws Exception {
this.project.getExtensions().getByType(ExtraPropertiesExtension.class).set("mainClassName", this.project.getPlugins().apply(ApplicationPlugin.class);
"com.example.MainClass"); JavaApplication extension = this.project.getExtensions().findByType(JavaApplication.class);
extension.setMainClassName("com.example.MainClass");
assertThat(this.convention.call()).isEqualTo("com.example.MainClass"); assertThat(this.convention.call()).isEqualTo("com.example.MainClass");
} }
...@@ -67,8 +69,9 @@ class MainClassConventionTests { ...@@ -67,8 +69,9 @@ class MainClassConventionTests {
@Test @Test
void springBootExtensionMainClassNameIsUsedInPreferenceToMainClassNameProjectProperty() throws Exception { void springBootExtensionMainClassNameIsUsedInPreferenceToMainClassNameProjectProperty() throws Exception {
this.project.getExtensions().getByType(ExtraPropertiesExtension.class).set("mainClassName", this.project.getPlugins().apply(ApplicationPlugin.class);
"com.example.ProjectPropertyMainClass"); JavaApplication javaApplication = this.project.getExtensions().findByType(JavaApplication.class);
javaApplication.setMainClassName("com.example.JavaApplicationMainClass");
SpringBootExtension extension = this.project.getExtensions().create("springBoot", SpringBootExtension.class, SpringBootExtension extension = this.project.getExtensions().create("springBoot", SpringBootExtension.class,
this.project); this.project);
extension.setMainClassName("com.example.SpringBootExtensionMainClass"); extension.setMainClassName("com.example.SpringBootExtensionMainClass");
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment