Simplify resolution of the application's main class name

Closes gh-30467
This commit is contained in:
Andy Wilkinson 2022-03-29 15:49:49 +01:00
parent 98cac100c6
commit 42ae55895d
6 changed files with 51 additions and 51 deletions

View File

@ -88,7 +88,7 @@ A number of configuration options that are specific to executable jars and wars
[[packaging-executable.configuring.main-class]]
=== Configuring the Main Class
By default, the executable archive's main class will be configured automatically by looking for a class with a `public static void main(String[])` method in directories on the task's classpath.
By default, the executable archive's main class will be configured automatically by looking for a class with a `public static void main(String[])` method in the main source set's output.
The main class can also be configured explicitly using the task's `mainClass` property:

View File

@ -11,7 +11,7 @@ The `bootRun` task is an instance of {boot-run-javadoc}[`BootRun`] which is a `J
As such, all of the {gradle-dsl}/org.gradle.api.tasks.JavaExec.html[usual configuration options] for executing a Java process in Gradle are available to you.
The task is automatically configured to use the runtime classpath of the main source set.
By default, the main class will be configured automatically by looking for a class with a `public static void main(String[])` method in directories on the task's classpath.
By default, the main class will be configured automatically by looking for a class with a `public static void main(String[])` method in the main source set's output.
The main class can also be configured explicitly using the task's `main` property:

View File

@ -35,6 +35,8 @@ import org.gradle.api.file.FileCollection;
import org.gradle.api.model.ObjectFactory;
import org.gradle.api.plugins.ApplicationPlugin;
import org.gradle.api.plugins.BasePlugin;
import org.gradle.api.plugins.ExtensionContainer;
import org.gradle.api.plugins.JavaApplication;
import org.gradle.api.plugins.JavaPlugin;
import org.gradle.api.plugins.JavaPluginExtension;
import org.gradle.api.provider.Provider;
@ -46,6 +48,7 @@ import org.gradle.api.tasks.compile.JavaCompile;
import org.gradle.jvm.toolchain.JavaToolchainService;
import org.gradle.jvm.toolchain.JavaToolchainSpec;
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;
@ -77,10 +80,11 @@ final class JavaPluginAction implements PluginApplicationAction {
classifyJarTask(project);
configureBuildTask(project);
configureDevelopmentOnlyConfiguration(project);
TaskProvider<BootJar> bootJar = configureBootJarTask(project);
TaskProvider<ResolveMainClassName> resolveMainClassName = configureResolveMainClassNameTask(project);
TaskProvider<BootJar> bootJar = configureBootJarTask(project, resolveMainClassName);
configureBootBuildImageTask(project, bootJar);
configureArtifactPublication(bootJar);
configureBootRunTask(project);
configureBootRunTask(project, resolveMainClassName);
project.afterEvaluate(this::configureUtf8Encoding);
configureParametersCompilerArg(project);
configureAdditionalMetadataLocations(project);
@ -96,7 +100,39 @@ final class JavaPluginAction implements PluginApplicationAction {
.configure((task) -> task.dependsOn(this.singlePublishedArtifact));
}
private TaskProvider<BootJar> configureBootJarTask(Project project) {
private TaskProvider<ResolveMainClassName> configureResolveMainClassNameTask(Project project) {
return project.getTasks().register(SpringBootPlugin.RESOLVE_MAIN_CLASS_NAME_TASK_NAME,
ResolveMainClassName.class, (resolveMainClassName) -> {
ExtensionContainer extensions = project.getExtensions();
resolveMainClassName.setDescription("Resolves the name of the application's main class.");
resolveMainClassName.setGroup(BasePlugin.BUILD_GROUP);
Callable<FileCollection> classpath = () -> project.getExtensions()
.getByType(SourceSetContainer.class).getByName(SourceSet.MAIN_SOURCE_SET_NAME).getOutput();
resolveMainClassName.setClasspath(classpath);
resolveMainClassName.getConfiguredMainClassName().convention(project.provider(() -> {
String javaApplicationMainClass = getJavaApplicationMainClass(extensions);
if (javaApplicationMainClass != null) {
return javaApplicationMainClass;
}
SpringBootExtension springBootExtension = project.getExtensions()
.findByType(SpringBootExtension.class);
return springBootExtension.getMainClass().getOrNull();
}));
resolveMainClassName.getOutputFile()
.set(project.getLayout().getBuildDirectory().file("resolvedMainClassName"));
});
}
private static String getJavaApplicationMainClass(ExtensionContainer extensions) {
JavaApplication javaApplication = extensions.findByType(JavaApplication.class);
if (javaApplication == null) {
return null;
}
return javaApplication.getMainClass().getOrNull();
}
private TaskProvider<BootJar> configureBootJarTask(Project project,
TaskProvider<ResolveMainClassName> resolveMainClassName) {
SourceSet mainSourceSet = javaPluginExtension(project).getSourceSets()
.getByName(SourceSet.MAIN_SOURCE_SET_NAME);
Configuration developmentOnly = project.getConfigurations()
@ -105,8 +141,6 @@ final class JavaPluginAction implements PluginApplicationAction {
.getByName(SpringBootPlugin.PRODUCTION_RUNTIME_CLASSPATH_CONFIGURATION_NAME);
Callable<FileCollection> classpath = () -> mainSourceSet.getRuntimeClasspath()
.minus((developmentOnly.minus(productionRuntimeClasspath))).filter(new JarTypeFileSpec());
TaskProvider<ResolveMainClassName> 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.");
@ -134,11 +168,9 @@ final class JavaPluginAction implements PluginApplicationAction {
this.singlePublishedArtifact.addJarCandidate(bootJar);
}
private void configureBootRunTask(Project project) {
private void configureBootRunTask(Project project, TaskProvider<ResolveMainClassName> resolveMainClassName) {
Callable<FileCollection> classpath = () -> javaPluginExtension(project).getSourceSets()
.findByName(SourceSet.MAIN_SOURCE_SET_NAME).getRuntimeClasspath().filter(new JarTypeFileSpec());
TaskProvider<ResolveMainClassName> 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);
@ -149,7 +181,7 @@ final class JavaPluginAction implements PluginApplicationAction {
}
return Collections.emptyList();
});
run.getMainClass().convention(resolveProvider.flatMap(ResolveMainClassName::readMainClassName));
run.getMainClass().convention(resolveMainClassName.flatMap(ResolveMainClassName::readMainClassName));
configureToolchainConvention(project, run);
});
}

View File

@ -23,7 +23,6 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.Objects;
import java.util.concurrent.Callable;
import org.gradle.api.DefaultTask;
import org.gradle.api.InvalidUserDataException;
@ -33,9 +32,6 @@ import org.gradle.api.Transformer;
import org.gradle.api.file.FileCollection;
import org.gradle.api.file.RegularFile;
import org.gradle.api.file.RegularFileProperty;
import org.gradle.api.plugins.BasePlugin;
import org.gradle.api.plugins.ExtensionContainer;
import org.gradle.api.plugins.JavaApplication;
import org.gradle.api.provider.Property;
import org.gradle.api.provider.Provider;
import org.gradle.api.tasks.Classpath;
@ -43,10 +39,8 @@ 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.gradle.work.DisableCachingByDefault;
import org.springframework.boot.gradle.dsl.SpringBootExtension;
import org.springframework.boot.loader.tools.MainClassFinder;
/**
@ -154,38 +148,6 @@ public class ResolveMainClassName extends DefaultTask {
return this.outputFile.map(new ClassNameReader());
}
static TaskProvider<ResolveMainClassName> registerForTask(String taskName, Project project,
Callable<FileCollection> classpath) {
TaskProvider<ResolveMainClassName> resolveMainClassNameProvider = project.getTasks()
.register(taskName + "MainClassName", ResolveMainClassName.class, (resolveMainClassName) -> {
ExtensionContainer extensions = project.getExtensions();
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(extensions);
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;
}
private static String getJavaApplicationMainClass(ExtensionContainer extensions) {
JavaApplication javaApplication = extensions.findByType(JavaApplication.class);
if (javaApplication == null) {
return null;
}
return javaApplication.getMainClass().getOrNull();
}
private static final class ClassNameReader implements Transformer<String, RegularFile> {
@Override

View File

@ -81,6 +81,12 @@ public class SpringBootPlugin implements Plugin<Project> {
*/
public static final String PRODUCTION_RUNTIME_CLASSPATH_CONFIGURATION_NAME = "productionRuntimeClasspath";
/**
* The name of the {@link ResolveMainClassName} task.
* @since 3.0.0
*/
public static final String RESOLVE_MAIN_CLASS_NAME_TASK_NAME = "resolveMainClassName";
/**
* The coordinates {@code (group:name:version)} of the
* {@code spring-boot-dependencies} bom.

View File

@ -76,8 +76,8 @@ class WarPluginAction implements PluginApplicationAction {
.getByName(SourceSet.MAIN_SOURCE_SET_NAME).getRuntimeClasspath()
.minus(providedRuntimeConfiguration(project)).minus((developmentOnly.minus(productionRuntimeClasspath)))
.filter(new JarTypeFileSpec());
TaskProvider<ResolveMainClassName> resolveMainClassName = ResolveMainClassName
.registerForTask(SpringBootPlugin.BOOT_WAR_TASK_NAME, project, classpath);
TaskProvider<ResolveMainClassName> resolveMainClassName = project.getTasks()
.named(SpringBootPlugin.RESOLVE_MAIN_CLASS_NAME_TASK_NAME, ResolveMainClassName.class);
TaskProvider<BootWar> bootWarProvider = project.getTasks().register(SpringBootPlugin.BOOT_WAR_TASK_NAME,
BootWar.class, (bootWar) -> {
bootWar.setGroup(BasePlugin.BUILD_GROUP);