From b1c4af40812f509a785ba8a6fd1006116ebd517c Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Tue, 13 Oct 2020 10:29:13 +0100 Subject: [PATCH] Update bootJar and bootWar to use new main class resolution mechanism See gh-22922 --- .../boot/gradle/plugin/JavaPluginAction.java | 106 ++++------------- .../gradle/plugin/MainClassConvention.java | 109 ------------------ .../gradle/plugin/ResolveMainClassName.java | 71 +++++++++++- .../boot/gradle/plugin/WarPluginAction.java | 45 +++++--- .../junit/GradleCompatibilityExtension.java | 3 +- .../plugin/MainClassConventionTests.java | 81 ------------- .../AbstractBootArchiveIntegrationTests.java | 22 ++++ ...rtClassIsSetByResolvingTheMainClass.gradle | 5 + ...rtClassIsSetByResolvingTheMainClass.gradle | 5 + 9 files changed, 152 insertions(+), 295 deletions(-) delete mode 100644 spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/MainClassConvention.java delete mode 100644 spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/plugin/MainClassConventionTests.java create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-startClassIsSetByResolvingTheMainClass.gradle create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-startClassIsSetByResolvingTheMainClass.gradle diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/JavaPluginAction.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/JavaPluginAction.java index df7a917554..ea76eb8e03 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/JavaPluginAction.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/JavaPluginAction.java @@ -17,15 +17,10 @@ package org.springframework.boot.gradle.plugin; import java.io.File; -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; import java.util.Collections; import java.util.List; import java.util.Optional; import java.util.Set; -import java.util.concurrent.Callable; import org.gradle.api.Action; import org.gradle.api.Plugin; @@ -37,13 +32,10 @@ import org.gradle.api.attributes.Bundling; import org.gradle.api.attributes.LibraryElements; import org.gradle.api.attributes.Usage; import org.gradle.api.file.FileCollection; -import org.gradle.api.file.RegularFileProperty; import org.gradle.api.internal.artifacts.dsl.LazyPublishArtifact; import org.gradle.api.model.ObjectFactory; import org.gradle.api.plugins.ApplicationPlugin; import org.gradle.api.plugins.BasePlugin; -import org.gradle.api.plugins.Convention; -import org.gradle.api.plugins.JavaApplication; import org.gradle.api.plugins.JavaPlugin; import org.gradle.api.plugins.JavaPluginConvention; import org.gradle.api.provider.Provider; @@ -51,7 +43,6 @@ import org.gradle.api.tasks.SourceSet; import org.gradle.api.tasks.TaskProvider; import org.gradle.api.tasks.compile.JavaCompile; -import org.springframework.boot.gradle.dsl.SpringBootExtension; import org.springframework.boot.gradle.tasks.bundling.BootBuildImage; import org.springframework.boot.gradle.tasks.bundling.BootJar; import org.springframework.boot.gradle.tasks.run.BootRun; @@ -86,8 +77,7 @@ final class JavaPluginAction implements PluginApplicationAction { TaskProvider bootJar = configureBootJarTask(project); configureBootBuildImageTask(project, bootJar); configureArtifactPublication(bootJar); - TaskProvider resolveMainClassName = configureResolveMainClassNameTask(project); - configureBootRunTask(project, resolveMainClassName); + configureBootRunTask(project); configureUtf8Encoding(project); configureParametersCompilerArg(project); configureAdditionalMetadataLocations(project); @@ -102,59 +92,26 @@ final class JavaPluginAction implements PluginApplicationAction { .configure((task) -> task.dependsOn(this.singlePublishedArtifact)); } - private TaskProvider configureResolveMainClassNameTask(Project project) { - Convention convention = project.getConvention(); - return project.getTasks().register("resolveMainClassName", ResolveMainClassName.class, - (resolveMainClassName) -> { - resolveMainClassName.setClasspath( - javaPluginConvention(project).getSourceSets().findByName(SourceSet.MAIN_SOURCE_SET_NAME) - .getRuntimeClasspath().filter(new JarTypeFileSpec())); - resolveMainClassName.getConfiguredMainClassName().convention(project.provider(() -> { - JavaApplication javaApplication = convention.findByType(JavaApplication.class); - String javaApplicationMainClass = null; - if (javaApplication != null) { - try { - javaApplicationMainClass = javaApplication.getMainClass().getOrNull(); - } - catch (NoSuchMethodError ex) { - javaApplicationMainClass = javaApplication.getMainClassName(); - } - } - if (javaApplicationMainClass != null) { - return javaApplicationMainClass; - } - SpringBootExtension springBootExtension = project.getExtensions() - .findByType(SpringBootExtension.class); - if (springBootExtension != null) { - return springBootExtension.getMainClass().getOrNull(); - } - return null; - })); - resolveMainClassName.getOutputFile() - .set(project.getLayout().getBuildDirectory().file("spring-boot-main-class-name")); - }); - } - private TaskProvider configureBootJarTask(Project project) { + SourceSet mainSourceSet = javaPluginConvention(project).getSourceSets() + .getByName(SourceSet.MAIN_SOURCE_SET_NAME); + Configuration developmentOnly = project.getConfigurations() + .getByName(SpringBootPlugin.DEVELOPMENT_ONLY_CONFIGURATION_NAME); + Configuration productionRuntimeClasspath = project.getConfigurations() + .getByName(SpringBootPlugin.PRODUCTION_RUNTIME_CLASSPATH_NAME); + FileCollection classpath = mainSourceSet.getRuntimeClasspath() + .minus((developmentOnly.minus(productionRuntimeClasspath))).filter(new JarTypeFileSpec()); + TaskProvider resolveMainClassName = ResolveMainClassName + .registerForTask(SpringBootPlugin.BOOT_JAR_TASK_NAME, project, classpath); return project.getTasks().register(SpringBootPlugin.BOOT_JAR_TASK_NAME, BootJar.class, (bootJar) -> { bootJar.setDescription( "Assembles an executable jar archive containing the main classes and their dependencies."); bootJar.setGroup(BasePlugin.BUILD_GROUP); - SourceSet mainSourceSet = javaPluginConvention(project).getSourceSets() - .getByName(SourceSet.MAIN_SOURCE_SET_NAME); - bootJar.classpath((Callable) () -> { - Configuration developmentOnly = project.getConfigurations() - .getByName(SpringBootPlugin.DEVELOPMENT_ONLY_CONFIGURATION_NAME); - Configuration productionRuntimeClasspath = project.getConfigurations() - .getByName(SpringBootPlugin.PRODUCTION_RUNTIME_CLASSPATH_NAME); - return mainSourceSet.getRuntimeClasspath().minus((developmentOnly.minus(productionRuntimeClasspath))) - .filter(new JarTypeFileSpec()); - }); - bootJar.getMainClass().convention(project.provider(() -> { - String manifestStartClass = (String) bootJar.getManifest().getAttributes().get("Start-Class"); - return (manifestStartClass != null) ? manifestStartClass - : new MainClassConvention(project, bootJar::getClasspath).call(); - })); + bootJar.classpath(classpath); + Provider manifestStartClass = project + .provider(() -> (String) bootJar.getManifest().getAttributes().get("Start-Class")); + bootJar.getMainClass().convention(resolveMainClassName.flatMap((resolver) -> manifestStartClass.isPresent() + ? manifestStartClass : resolveMainClassName.get().readMainClassName())); }); } @@ -172,41 +129,28 @@ final class JavaPluginAction implements PluginApplicationAction { this.singlePublishedArtifact.addCandidate(artifact); } - private void configureBootRunTask(Project project, TaskProvider resolveMainClassName) { + private void configureBootRunTask(Project project) { + FileCollection classpath = javaPluginConvention(project).getSourceSets() + .findByName(SourceSet.MAIN_SOURCE_SET_NAME).getRuntimeClasspath().filter(new JarTypeFileSpec()); + TaskProvider resolveProvider = ResolveMainClassName.registerForTask("bootRun", project, + classpath); project.getTasks().register("bootRun", BootRun.class, (run) -> { run.setDescription("Runs this project as a Spring Boot application."); run.setGroup(ApplicationPlugin.APPLICATION_GROUP); - run.classpath(javaPluginConvention(project).getSourceSets().findByName(SourceSet.MAIN_SOURCE_SET_NAME) - .getRuntimeClasspath().filter(new JarTypeFileSpec())); + run.classpath(classpath); run.getConventionMapping().map("jvmArgs", () -> { if (project.hasProperty("applicationDefaultJvmArgs")) { return project.property("applicationDefaultJvmArgs"); } return Collections.emptyList(); }); - run.dependsOn(resolveMainClassName); - run.getInputs().file(resolveMainClassName.map((task) -> task.getOutputFile())); try { - run.getMainClass().set(resolveMainClassName.flatMap((task) -> readMainClassName(task.getOutputFile()))); + run.getMainClass().convention(resolveProvider.flatMap(ResolveMainClassName::readMainClassName)); } catch (NoSuchMethodError ex) { + run.getInputs().file(resolveProvider.map((task) -> task.getOutputFile())); run.conventionMapping("main", - () -> resolveMainClassName.flatMap((task) -> readMainClassName(task.getOutputFile())).get()); - } - }); - } - - private Provider readMainClassName(RegularFileProperty outputFile) { - return outputFile.map((file) -> { - Path output = file.getAsFile().toPath(); - if (!Files.exists(output)) { - return null; - } - try { - return new String(Files.readAllBytes(output), StandardCharsets.UTF_8); - } - catch (IOException ex) { - throw new RuntimeException("Failed to read main class name from '" + output + "'"); + () -> resolveProvider.flatMap(ResolveMainClassName::readMainClassName).get()); } }); } 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 deleted file mode 100644 index 8d14be4978..0000000000 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/MainClassConvention.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright 2012-2020 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 - * - * https://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.File; -import java.io.IOException; -import java.lang.reflect.Method; -import java.util.Objects; -import java.util.concurrent.Callable; -import java.util.function.Supplier; - -import org.gradle.api.InvalidUserDataException; -import org.gradle.api.Project; -import org.gradle.api.file.FileCollection; -import org.gradle.api.plugins.JavaApplication; -import org.gradle.api.provider.Property; - -import org.springframework.boot.gradle.dsl.SpringBootExtension; -import org.springframework.boot.loader.tools.MainClassFinder; - -/** - * A {@link Callable} that provides a convention for the project's main class name. - * - * @author Andy Wilkinson - */ -final class MainClassConvention implements Callable { - - private static final String SPRING_BOOT_APPLICATION_CLASS_NAME = "org.springframework.boot.autoconfigure.SpringBootApplication"; - - private final Project project; - - private final Supplier classpathSupplier; - - MainClassConvention(Project project, Supplier classpathSupplier) { - this.project = project; - this.classpathSupplier = classpathSupplier; - } - - @Override - public String call() throws Exception { - SpringBootExtension springBootExtension = this.project.getExtensions().findByType(SpringBootExtension.class); - if (springBootExtension != null) { - String mainClass = springBootExtension.getMainClass().getOrNull(); - if (mainClass != null) { - return mainClass; - } - } - String javaApplicationMainClass = getJavaApplicationMainClass(); - return (javaApplicationMainClass != null) ? javaApplicationMainClass : resolveMainClass(); - } - - @SuppressWarnings({ "unchecked", "deprecation" }) - private String getJavaApplicationMainClass() { - JavaApplication javaApplication = this.project.getConvention().findByType(JavaApplication.class); - if (javaApplication == null) { - return null; - } - Method getMainClass = findMethod(JavaApplication.class, "getMainClass"); - if (getMainClass != null) { - try { - Property mainClass = (Property) getMainClass.invoke(javaApplication); - return mainClass.getOrElse(null); - } - catch (Exception ex) { - // Continue - } - } - return javaApplication.getMainClassName(); - } - - private static Method findMethod(Class type, String name) { - for (Method candidate : type.getMethods()) { - if (candidate.getName().equals(name)) { - return candidate; - } - } - return null; - } - - private String resolveMainClass() { - return this.classpathSupplier.get().filter(File::isDirectory).getFiles().stream().map(this::findMainClass) - .filter(Objects::nonNull).findFirst().orElseThrow(() -> new InvalidUserDataException( - "Main class name has not been configured and it could not be resolved")); - } - - private String findMainClass(File file) { - try { - return MainClassFinder.findSingleMainClass(file, SPRING_BOOT_APPLICATION_CLASS_NAME); - } - catch (IOException ex) { - return null; - } - } - -} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/ResolveMainClassName.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/ResolveMainClassName.java index e32fd195b4..2a0f489946 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/ResolveMainClassName.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/ResolveMainClassName.java @@ -20,21 +20,29 @@ import java.io.File; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; +import java.nio.file.Path; import java.nio.file.StandardOpenOption; import java.util.Objects; import org.gradle.api.DefaultTask; import org.gradle.api.InvalidUserDataException; +import org.gradle.api.Project; import org.gradle.api.Task; import org.gradle.api.file.FileCollection; import org.gradle.api.file.RegularFileProperty; +import org.gradle.api.plugins.BasePlugin; +import org.gradle.api.plugins.Convention; +import org.gradle.api.plugins.JavaApplication; import org.gradle.api.provider.Property; +import org.gradle.api.provider.Provider; import org.gradle.api.tasks.Classpath; import org.gradle.api.tasks.Input; import org.gradle.api.tasks.Optional; import org.gradle.api.tasks.OutputFile; import org.gradle.api.tasks.TaskAction; +import org.gradle.api.tasks.TaskProvider; +import org.springframework.boot.gradle.dsl.SpringBootExtension; import org.springframework.boot.loader.tools.MainClassFinder; /** @@ -102,11 +110,11 @@ public class ResolveMainClassName extends DefaultTask { @TaskAction void resolveAndStoreMainClassName() throws IOException { - String mainClassName = resolveMainClassName(); File outputFile = this.outputFile.getAsFile().get(); outputFile.getParentFile().mkdirs(); - Files.write(this.outputFile.get().getAsFile().toPath(), mainClassName.getBytes(StandardCharsets.UTF_8), - StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); + String mainClassName = resolveMainClassName(); + Files.write(outputFile.toPath(), mainClassName.getBytes(StandardCharsets.UTF_8), StandardOpenOption.WRITE, + StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); } private String resolveMainClassName() { @@ -115,8 +123,7 @@ public class ResolveMainClassName extends DefaultTask { return configuredMainClass; } return getClasspath().filter(File::isDirectory).getFiles().stream().map(this::findMainClass) - .filter(Objects::nonNull).findFirst().orElseThrow(() -> new InvalidUserDataException( - "Main class name has not been configured and it could not be resolved")); + .filter(Objects::nonNull).findFirst().orElse(""); } private String findMainClass(File file) { @@ -128,4 +135,58 @@ public class ResolveMainClassName extends DefaultTask { } } + Provider readMainClassName() { + return this.outputFile.map((file) -> { + if (file.getAsFile().length() == 0) { + throw new InvalidUserDataException( + "Main class name has not been configured and it could not be resolved"); + } + Path output = file.getAsFile().toPath(); + try { + return new String(Files.readAllBytes(output), StandardCharsets.UTF_8); + } + catch (IOException ex) { + throw new RuntimeException("Failed to read main class name from '" + output + "'"); + } + }); + } + + static TaskProvider registerForTask(String taskName, Project project, + FileCollection classpath) { + TaskProvider resolveMainClassNameProvider = project.getTasks() + .register(taskName + "MainClassName", ResolveMainClassName.class, (resolveMainClassName) -> { + Convention convention = project.getConvention(); + resolveMainClassName.setDescription( + "Resolves the name of the application's main class for the " + taskName + " task."); + resolveMainClassName.setGroup(BasePlugin.BUILD_GROUP); + resolveMainClassName.setClasspath(classpath); + resolveMainClassName.getConfiguredMainClassName().convention(project.provider(() -> { + String javaApplicationMainClass = getJavaApplicationMainClass(convention); + if (javaApplicationMainClass != null) { + return javaApplicationMainClass; + } + SpringBootExtension springBootExtension = project.getExtensions() + .findByType(SpringBootExtension.class); + return springBootExtension.getMainClass().getOrNull(); + })); + resolveMainClassName.getOutputFile() + .set(project.getLayout().getBuildDirectory().file(taskName + "MainClassName")); + }); + return resolveMainClassNameProvider; + } + + @SuppressWarnings("deprecation") + private static String getJavaApplicationMainClass(Convention convention) { + JavaApplication javaApplication = convention.findByType(JavaApplication.class); + if (javaApplication == null) { + return null; + } + try { + return javaApplication.getMainClass().getOrNull(); + } + catch (NoSuchMethodError ex) { + return javaApplication.getMainClassName(); + } + } + } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/WarPluginAction.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/WarPluginAction.java index b945d4c9a0..310322cd48 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/WarPluginAction.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/WarPluginAction.java @@ -25,6 +25,9 @@ import org.gradle.api.file.FileCollection; import org.gradle.api.internal.artifacts.dsl.LazyPublishArtifact; import org.gradle.api.plugins.BasePlugin; import org.gradle.api.plugins.WarPlugin; +import org.gradle.api.provider.Provider; +import org.gradle.api.tasks.SourceSet; +import org.gradle.api.tasks.SourceSetContainer; import org.gradle.api.tasks.TaskProvider; import org.springframework.boot.gradle.tasks.bundling.BootWar; @@ -60,23 +63,31 @@ class WarPluginAction implements PluginApplicationAction { } private TaskProvider configureBootWarTask(Project project) { - return project.getTasks().register(SpringBootPlugin.BOOT_WAR_TASK_NAME, BootWar.class, (bootWar) -> { - bootWar.setGroup(BasePlugin.BUILD_GROUP); - bootWar.setDescription("Assembles an executable war archive containing webapp" - + " content, and the main classes and their dependencies."); - bootWar.providedClasspath(providedRuntimeConfiguration(project)); - Configuration developmentOnly = project.getConfigurations() - .getByName(SpringBootPlugin.DEVELOPMENT_ONLY_CONFIGURATION_NAME); - Configuration productionRuntimeClasspath = project.getConfigurations() - .getByName(SpringBootPlugin.PRODUCTION_RUNTIME_CLASSPATH_NAME); - bootWar.setClasspath(bootWar.getClasspath().minus((developmentOnly.minus(productionRuntimeClasspath))) - .filter(new JarTypeFileSpec())); - bootWar.getMainClass().convention(project.provider(() -> { - String manifestStartClass = (String) bootWar.getManifest().getAttributes().get("Start-Class"); - return (manifestStartClass != null) ? manifestStartClass - : new MainClassConvention(project, bootWar::getClasspath).call(); - })); - }); + Configuration developmentOnly = project.getConfigurations() + .getByName(SpringBootPlugin.DEVELOPMENT_ONLY_CONFIGURATION_NAME); + Configuration productionRuntimeClasspath = project.getConfigurations() + .getByName(SpringBootPlugin.PRODUCTION_RUNTIME_CLASSPATH_NAME); + FileCollection classpath = project.getConvention().getByType(SourceSetContainer.class) + .getByName(SourceSet.MAIN_SOURCE_SET_NAME).getRuntimeClasspath() + .minus(providedRuntimeConfiguration(project)).minus((developmentOnly.minus(productionRuntimeClasspath))) + .filter(new JarTypeFileSpec()); + TaskProvider resolveMainClassName = ResolveMainClassName + .registerForTask(SpringBootPlugin.BOOT_WAR_TASK_NAME, project, classpath); + TaskProvider bootWarProvider = project.getTasks().register(SpringBootPlugin.BOOT_WAR_TASK_NAME, + BootWar.class, (bootWar) -> { + bootWar.setGroup(BasePlugin.BUILD_GROUP); + bootWar.setDescription("Assembles an executable war archive containing webapp" + + " content, and the main classes and their dependencies."); + bootWar.providedClasspath(providedRuntimeConfiguration(project)); + bootWar.setClasspath(classpath); + Provider manifestStartClass = project + .provider(() -> (String) bootWar.getManifest().getAttributes().get("Start-Class")); + bootWar.getMainClass() + .convention(resolveMainClassName.flatMap((resolver) -> manifestStartClass.isPresent() + ? manifestStartClass : resolveMainClassName.get().readMainClassName())); + }); + bootWarProvider.map((bootWar) -> bootWar.getClasspath()); + return bootWarProvider; } private FileCollection providedRuntimeConfiguration(Project project) { 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 18fe04cdf7..d2d360730d 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 @@ -66,8 +66,7 @@ final class GradleCompatibilityExtension implements TestTemplateInvocationContex boolean configurationCache = AnnotationUtils .findAnnotation(context.getRequiredTestClass(), GradleCompatibility.class).get() .configurationCache(); - if (configurationCache - && GradleVersion.version(version).compareTo(GradleVersion.version("6.7-rc-1")) >= 0) { + if (configurationCache && GradleVersion.version(version).compareTo(GradleVersion.version("6.7")) >= 0) { invocationContexts.add(new GradleVersionTestTemplateInvocationContext(version, true)); } return invocationContexts.stream(); 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 deleted file mode 100644 index c67c920638..0000000000 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/plugin/MainClassConventionTests.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright 2012-2020 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 - * - * https://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.File; -import java.io.IOException; - -import org.gradle.api.Project; -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; -import org.junit.jupiter.api.io.TempDir; - -import org.springframework.boot.gradle.dsl.SpringBootExtension; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Integration tests for {@link MainClassConvention}. - * - * @author Andy Wilkinson - */ -class MainClassConventionTests { - - @TempDir - File temp; - - private Project project; - - private MainClassConvention convention; - - @BeforeEach - void createConvention() throws IOException { - this.project = ProjectBuilder.builder().withProjectDir(this.temp).build(); - this.convention = new MainClassConvention(this.project, () -> null); - } - - @Test - void javaApplicationExtensionMainClassNameIsUsed() throws Exception { - this.project.getPlugins().apply(ApplicationPlugin.class); - JavaApplication extension = this.project.getExtensions().findByType(JavaApplication.class); - extension.getMainClass().set("com.example.MainClass"); - assertThat(this.convention.call()).isEqualTo("com.example.MainClass"); - } - - @Test - void springBootExtensionMainClassNameIsUsed() throws Exception { - SpringBootExtension extension = this.project.getExtensions().create("springBoot", SpringBootExtension.class, - this.project); - extension.getMainClass().set("com.example.MainClass"); - assertThat(this.convention.call()).isEqualTo("com.example.MainClass"); - } - - @Test - void springBootExtensionMainClassNameIsUsedInPreferenceToJavaApplicationExtensionMainClassName() throws Exception { - this.project.getPlugins().apply(ApplicationPlugin.class); - JavaApplication javaApplication = this.project.getExtensions().findByType(JavaApplication.class); - javaApplication.getMainClass().set("com.example.JavaApplicationMainClass"); - SpringBootExtension extension = this.project.getExtensions().create("springBoot", SpringBootExtension.class, - this.project); - extension.getMainClass().set("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 c4e2476281..d2f5b06c39 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 @@ -34,6 +34,7 @@ import org.junit.jupiter.api.TestTemplate; import org.springframework.boot.gradle.testkit.GradleBuild; import org.springframework.boot.loader.tools.FileUtils; +import org.springframework.util.FileSystemUtils; import static org.assertj.core.api.Assertions.assertThat; @@ -188,6 +189,27 @@ abstract class AbstractBootArchiveIntegrationTests { } } + @TestTemplate + void startClassIsSetByResolvingTheMainClass() throws IOException { + copyMainClassApplication(); + 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])) { + Attributes mainAttributes = jarFile.getManifest().getMainAttributes(); + assertThat(mainAttributes.getValue("Start-Class")).isEqualTo("com.example.main.CustomMainClass"); + } + } + + private void copyMainClassApplication() throws IOException { + copyApplication("main"); + } + + private void copyApplication(String name) throws IOException { + File output = new File(this.gradleBuild.getProjectDir(), "src/main/java/com/example/" + name); + output.mkdirs(); + FileSystemUtils.copyRecursively(new File("src/test/java/com/example/" + name), output); + } + private void createStandardJar(File location) throws IOException { createJar(location, (attributes) -> { }); diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-startClassIsSetByResolvingTheMainClass.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-startClassIsSetByResolvingTheMainClass.gradle new file mode 100644 index 0000000000..617daecbe6 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-startClassIsSetByResolvingTheMainClass.gradle @@ -0,0 +1,5 @@ +plugins { + id 'java' + id 'application' + id 'org.springframework.boot' version '{version}' +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-startClassIsSetByResolvingTheMainClass.gradle b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-startClassIsSetByResolvingTheMainClass.gradle new file mode 100644 index 0000000000..0ee67eb10d --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-startClassIsSetByResolvingTheMainClass.gradle @@ -0,0 +1,5 @@ +plugins { + id 'war' + id 'application' + id 'org.springframework.boot' version '{version}' +}