diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/asciidoc/packaging.adoc b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/asciidoc/packaging.adoc index e435d6a50fc..9fd3643fe8b 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/asciidoc/packaging.adoc +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/asciidoc/packaging.adoc @@ -93,8 +93,16 @@ property: include::../gradle/packaging/boot-jar-main-class.gradle[tags=main-class] ---- -Alternatively, if the {application-plugin}[`application` plugin] has been applied -its `mainClassName` project property can be used: +Alternatively, the main class name can be configured project-wide using the +`mainClassName` property of the Spring Boot DSL: + +[source,groovy,indent=0,subs="verbatim"] +---- +include::../gradle/packaging/spring-boot-dsl-main-class.gradle[tags=main-class] +---- + +If the {application-plugin}[`application` plugin] has been applied its `mainClassName` +project property can be used for the same purpose: [source,groovy,indent=0,subs="verbatim"] ---- 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 c4f2c65dedd..e87e9d01a70 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 @@ -19,8 +19,16 @@ classpath. The main class can also be configured explicitly using the task's include::../gradle/running/boot-run-main.gradle[tags=main] ---- -Alternatively, if the {application-plugin}[`application` plugin] has been applied -its `mainClassName` project property can be used: +Alternatively, the main class name can be configured project-wide using the +`mainClassName` property of the Spring Boot DSL: + +[source,groovy,indent=0,subs="verbatim"] +---- +include::../gradle/running/spring-boot-dsl-main-class-name.gradle[tags=main-class] +---- + +If the {application-plugin}[`application` plugin] has been applied its `mainClassName` +project property can be used for the same purpose: [source,groovy,indent=0,subs="verbatim"] ---- diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/gradle/packaging/spring-boot-dsl-main-class.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/gradle/packaging/spring-boot-dsl-main-class.gradle new file mode 100644 index 00000000000..f2f12ecbea7 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/gradle/packaging/spring-boot-dsl-main-class.gradle @@ -0,0 +1,14 @@ +buildscript { + dependencies { + classpath files(pluginClasspath.split(',')) + } +} + +apply plugin: 'org.springframework.boot' +apply plugin: 'java' + +// tag::main-class[] +springBoot { + mainClassName = 'com.example.ExampleApplication' +} +// end::main-class[] diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/gradle/running/spring-boot-dsl-main-class-name.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/gradle/running/spring-boot-dsl-main-class-name.gradle new file mode 100644 index 00000000000..27737faf64e --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/gradle/running/spring-boot-dsl-main-class-name.gradle @@ -0,0 +1,21 @@ +buildscript { + dependencies { + classpath files(pluginClasspath.split(',')) + } +} + +apply plugin: 'org.springframework.boot' +apply plugin: 'java' +apply plugin: 'application' + +// tag::main-class[] +springBoot { + mainClassName = 'com.example.ExampleApplication' +} +// end::main-class[] + +task configuredMainClass { + doLast { + println bootRun.mainClassName + } +} 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 9429f1304f4..0f86ea2f45f 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 @@ -39,6 +39,8 @@ public class SpringBootExtension { private final Project project; + private String mainClassName; + /** * Creates a new {@code SpringBootPluginExtension} that is associated with the given * {@code project}. @@ -49,6 +51,24 @@ public class SpringBootExtension { this.project = project; } + /** + * Returns the main class name of the application. + * + * @return the name of the application's main class + */ + public String getMainClassName() { + return this.mainClassName; + } + + /** + * Sets the main class name of the application. + * + * @param mainClassName the name of the application's main class + */ + public void setMainClassName(String mainClassName) { + this.mainClassName = mainClassName; + } + /** * Creates a new {@link BuildInfo} task named {@code bootBuildInfo} and configures the * Java plugin's {@code classes} task to depend upon it. 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 e8ad30fe47c..27ef3c23633 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 java.util.function.Supplier; import org.gradle.api.Project; import org.gradle.api.file.FileCollection; +import org.springframework.boot.gradle.dsl.SpringBootExtension; import org.springframework.boot.loader.tools.MainClassFinder; /** @@ -47,6 +48,12 @@ final class MainClassConvention implements Callable { @Override public String call() throws Exception { + SpringBootExtension springBootExtension = this.project.getExtensions() + .findByType(SpringBootExtension.class); + if (springBootExtension != null + && springBootExtension.getMainClassName() != null) { + return springBootExtension.getMainClassName(); + } if (this.project.hasProperty("mainClassName")) { Object mainClassName = this.project.property("mainClassName"); if (mainClassName != null) { diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/docs/PackagingDocumentationTests.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/docs/PackagingDocumentationTests.java index 683390b1882..3ac348fa8f5 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/docs/PackagingDocumentationTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/docs/PackagingDocumentationTests.java @@ -89,6 +89,20 @@ public class PackagingDocumentationTests { } } + @Test + public void springBootDslMainClass() throws IOException { + this.gradleBuild + .script("src/main/gradle/packaging/spring-boot-dsl-main-class.gradle") + .build("bootJar"); + File file = new File(this.gradleBuild.getProjectDir(), + "build/libs/" + this.gradleBuild.getProjectDir().getName() + ".jar"); + assertThat(file).isFile(); + try (JarFile jar = new JarFile(file)) { + assertThat(jar.getManifest().getMainAttributes().getValue("Start-Class")) + .isEqualTo("com.example.ExampleApplication"); + } + } + @Test public void bootWarIncludeDevtools() throws IOException { new File(this.gradleBuild.getProjectDir(), 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 05a6eb1dbcc..0530c994292 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 @@ -51,6 +51,14 @@ public class RunningDocumentationTests { .contains("com.example.ExampleApplication"); } + @Test + public void springBootDslMainClassName() throws IOException { + assertThat(this.gradleBuild + .script("src/main/gradle/running/spring-boot-dsl-main-class-name.gradle") + .build("configuredMainClass").getOutput()) + .contains("com.example.ExampleApplication"); + } + @Test public void bootRunSourceResources() throws IOException { assertThat(this.gradleBuild 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 new file mode 100644 index 00000000000..bb11b7cb02b --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/plugin/MainClassConventionTests.java @@ -0,0 +1,76 @@ +/* + * Copyright 2012-2017 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 + * + * http://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.IOException; + +import org.gradle.api.Project; +import org.gradle.api.plugins.ExtraPropertiesExtension; +import org.gradle.internal.impldep.org.junit.Before; +import org.gradle.internal.impldep.org.junit.Rule; +import org.gradle.internal.impldep.org.junit.Test; +import org.gradle.internal.impldep.org.junit.rules.TemporaryFolder; +import org.gradle.testfixtures.ProjectBuilder; + +import org.springframework.boot.gradle.dsl.SpringBootExtension; + +import static org.assertj.core.api.Assertions.assertThat; + +public class MainClassConventionTests { + + @Rule + public final TemporaryFolder temp = new TemporaryFolder(); + + private Project project; + + private MainClassConvention convention; + + @Before + public void createConvention() throws IOException { + this.project = ProjectBuilder.builder().withProjectDir(this.temp.newFolder()) + .build(); + this.convention = new MainClassConvention(this.project, () -> null); + } + + @Test + public void mainClassNameProjectPropertyIsUsed() throws Exception { + this.project.getExtensions().getByType(ExtraPropertiesExtension.class) + .set("mainClassName", "com.example.MainClass"); + assertThat(this.convention.call()).isEqualTo("com.example.MainClass"); + } + + @Test + public void springBootExtensionMainClassNameIsUsed() throws Exception { + SpringBootExtension extension = this.project.getExtensions().create("springBoot", + SpringBootExtension.class, this.project); + extension.setMainClassName("com.example.MainClass"); + assertThat(this.convention.call()).isEqualTo("com.example.MainClass"); + } + + @Test + public void springBootExtensionMainClassNameIsUsedInPreferenceToMainClassNameProjectProperty() + throws Exception { + this.project.getExtensions().getByType(ExtraPropertiesExtension.class) + .set("mainClassName", "com.example.ProjectPropertyMainClass"); + SpringBootExtension extension = this.project.getExtensions().create("springBoot", + SpringBootExtension.class, this.project); + extension.setMainClassName("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 27b8f332782..a5d033e3f78 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 @@ -112,7 +112,18 @@ public abstract class AbstractBootArchiveIntegrationTests { assertThat(jarFile.getManifest().getMainAttributes().getValue("Start-Class")) .isEqualTo("com.example.CustomMain"); } + } + @Test + public void springBootExtensionMainClassNameIsUsed() throws IOException { + 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])) { + assertThat(jarFile.getManifest().getMainAttributes().getValue("Start-Class")) + .isEqualTo("com.example.CustomMain"); + } } @Test 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 b12cbc8ccd7..c17c13e3a87 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 @@ -96,6 +96,15 @@ public class BootRunIntegrationTests { .doesNotContain(canonicalPathOf("build/resources/main")); } + @Test + public 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"); + } + @Test public void applicationPluginMainClassNameIsUsed() throws IOException { BuildResult result = this.gradleBuild.build("echoMainClassName"); diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-springBootExtensionMainClassNameIsUsed.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-springBootExtensionMainClassNameIsUsed.gradle new file mode 100644 index 00000000000..8028ff41694 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-springBootExtensionMainClassNameIsUsed.gradle @@ -0,0 +1,13 @@ +buildscript { + dependencies { + classpath files(pluginClasspath.split(',')) + } +} + +apply plugin: 'java' +apply plugin: 'org.springframework.boot' +apply plugin: 'application' + +springBoot { + 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/bundling/BootWarIntegrationTests-springBootExtensionMainClassNameIsUsed.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-springBootExtensionMainClassNameIsUsed.gradle new file mode 100644 index 00000000000..49f3a1ae174 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-springBootExtensionMainClassNameIsUsed.gradle @@ -0,0 +1,13 @@ +buildscript { + dependencies { + classpath files(pluginClasspath.split(',')) + } +} + +apply plugin: 'war' +apply plugin: 'org.springframework.boot' +apply plugin: 'application' + +springBoot { + 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-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 new file mode 100644 index 00000000000..b4730f3ae8b --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/run/BootRunIntegrationTests-springBootExtensionMainClassNameIsUsed.gradle @@ -0,0 +1,16 @@ +buildscript { + dependencies { + classpath files(pluginClasspath.split(',')) + } +} + +apply plugin: 'application' +apply plugin: 'org.springframework.boot' + +springBoot { + mainClassName = 'com.example.CustomMainClass' +} + +task echoMainClassName { + println 'Main class name = ' + bootRun.mainClassName +}