Skip to content

Commit

Permalink
Support Gradle 6.0
Browse files Browse the repository at this point in the history
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
  • Loading branch information
wilkinsona committed Nov 11, 2019
1 parent f9dc815 commit 379ba0d
Show file tree
Hide file tree
Showing 12 changed files with 134 additions and 29 deletions.
Expand Up @@ -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)
|===


Expand Down Expand Up @@ -195,7 +195,7 @@ In those cases, see <<using-spring-boot.adoc#using-boot-maven-without-a-parent>>

[[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.

Expand Down
Expand Up @@ -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.

Expand Down
Expand Up @@ -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.
Expand Up @@ -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
Expand Down
Expand Up @@ -5,7 +5,9 @@ plugins {
}

// tag::main-class[]
mainClassName = 'com.example.ExampleApplication'
application {
mainClassName = 'com.example.ExampleApplication'
}
// end::main-class[]

task configuredMainClass {
Expand Down
Expand Up @@ -17,13 +17,15 @@
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;
import org.gradle.api.plugins.BasePlugin;
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;
Expand Down Expand Up @@ -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() {
Expand All @@ -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;
}

}
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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
Expand All @@ -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<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
public Class<? extends Plugin<Project>> getPluginClass() {
return ApplicationPlugin.class;
Expand Down
Expand Up @@ -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;
Expand Down Expand Up @@ -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();
}
Expand Down
Expand Up @@ -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;
Expand All @@ -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;

Expand Down Expand Up @@ -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()) {
Expand All @@ -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"));
}
Expand Down
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}

/**
Expand Down
Expand Up @@ -39,7 +39,7 @@
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",
"5.4.1", "5.5.1", "5.6.4");
"5.4.1", "5.5.1", "5.6.4", "6.0");

@Override
public Stream<TestTemplateInvocationContext> provideTestTemplateInvocationContexts(ExtensionContext context) {
Expand Down
Expand Up @@ -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;
Expand Down Expand Up @@ -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");
}

Expand All @@ -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");
Expand Down

0 comments on commit 379ba0d

Please sign in to comment.