Simplify resolution of the application's main class name

Closes gh-30467
pull/30498/head
Andy Wilkinson 3 years ago
parent 98cac100c6
commit 42ae55895d

@ -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:

@ -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:

@ -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);
});
}

@ -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

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

@ -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);

Loading…
Cancel
Save