diff --git a/spring-boot-tools/spring-boot-gradle-plugin/src/main/asciidoc/running.adoc b/spring-boot-tools/spring-boot-gradle-plugin/src/main/asciidoc/running.adoc index 722c481e2c..419fd60b43 100644 --- a/spring-boot-tools/spring-boot-gradle-plugin/src/main/asciidoc/running.adoc +++ b/spring-boot-tools/spring-boot-gradle-plugin/src/main/asciidoc/running.adoc @@ -8,16 +8,11 @@ To run your application without first building an archive use the `bootRun` task $ ./gradlew bootRun ---- -The `bootRun` task is an instance of -{boot-run-javadoc}[`BootRun`] which is a `JavaExec` subclass. 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. - -The main class can also be configured explicitly using the task's `main` property: +The `bootRun` task is automatically configured to use the runtime classpath of the +main source set. By default, the main class will be discovered by looking for a class +with a `public static void main(String[])` method in directories on the task's +classpath. The main class can also be configured explicitly using the task's `main` +property: [source,groovy,indent=0,subs="verbatim"] ---- @@ -32,6 +27,15 @@ its `mainClassName` project property can be used: include::../gradle/running/application-plugin-main-class-name.gradle[tags=main-class] ---- +Two properties, `args` and `jvmArgs`, are also provided for configuring the +arguments and JVM arguments that are used to run the application. + +For more advanced configuration the `JavaExecSpec` that is used can be customized: + +[source,groovy,indent=0,subs="verbatim"] +---- +include::../gradle/running/boot-run-custom-exec-spec.gradle[tags=customization] +---- [[running-your-application-reloading-resources]] diff --git a/spring-boot-tools/spring-boot-gradle-plugin/src/main/gradle/running/boot-run-custom-exec-spec.gradle b/spring-boot-tools/spring-boot-gradle-plugin/src/main/gradle/running/boot-run-custom-exec-spec.gradle new file mode 100644 index 0000000000..dcfaf36528 --- /dev/null +++ b/spring-boot-tools/spring-boot-gradle-plugin/src/main/gradle/running/boot-run-custom-exec-spec.gradle @@ -0,0 +1,16 @@ +buildscript { + dependencies { + classpath files(pluginClasspath.split(',')) + } +} + +apply plugin: 'org.springframework.boot' +apply plugin: 'java' + +// tag::customization[] +bootRun { + execSpec { + systemProperty 'com.example.foo', 'bar' + } +} +// end::customization[] diff --git a/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/JavaPluginAction.java b/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/JavaPluginAction.java index 1359dfd905..89269d7710 100644 --- a/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/JavaPluginAction.java +++ b/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/JavaPluginAction.java @@ -97,6 +97,7 @@ final class JavaPluginAction implements PluginApplicationAction { this.singlePublishedArtifact.addCandidate(artifact); } + @SuppressWarnings("unchecked") private void configureBootRunTask(Project project) { JavaPluginConvention javaConvention = project.getConvention() .getPlugin(JavaPluginConvention.class); @@ -105,14 +106,14 @@ final class JavaPluginAction implements PluginApplicationAction { run.setGroup(ApplicationPlugin.APPLICATION_GROUP); run.classpath(javaConvention.getSourceSets() .findByName(SourceSet.MAIN_SOURCE_SET_NAME).getRuntimeClasspath()); - run.getConventionMapping().map("jvmArgs", () -> { + run.setJvmArgs(project.provider(() -> { if (project.hasProperty("applicationDefaultJvmArgs")) { - return project.property("applicationDefaultJvmArgs"); + return (List) project.property("applicationDefaultJvmArgs"); } return Collections.emptyList(); - }); - run.conventionMapping("main", - new MainClassConvention(project, run::getClasspath)); + })); + run.setMain( + project.provider(new MainClassConvention(project, run::getClasspath))); } private void configureUtf8Encoding(Project project) { diff --git a/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/MainClassConvention.java b/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/MainClassConvention.java index 445c751dc3..e8ad30fe47 100644 --- a/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/MainClassConvention.java +++ b/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/MainClassConvention.java @@ -32,7 +32,7 @@ import org.springframework.boot.loader.tools.MainClassFinder; * * @author Andy Wilkinson */ -final class MainClassConvention implements Callable { +final class MainClassConvention implements Callable { private static final String SPRING_BOOT_APPLICATION_CLASS_NAME = "org.springframework.boot.autoconfigure.SpringBootApplication"; @@ -46,11 +46,11 @@ final class MainClassConvention implements Callable { } @Override - public Object call() throws Exception { + public String call() throws Exception { if (this.project.hasProperty("mainClassName")) { Object mainClassName = this.project.property("mainClassName"); if (mainClassName != null) { - return mainClassName; + return mainClassName.toString(); } } return resolveMainClass(); diff --git a/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/run/BootRun.java b/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/run/BootRun.java index 1b76bc34a0..a770e0fd08 100644 --- a/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/run/BootRun.java +++ b/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/run/BootRun.java @@ -16,10 +16,21 @@ package org.springframework.boot.gradle.tasks.run; +import java.util.ArrayList; +import java.util.List; + +import org.gradle.api.Action; +import org.gradle.api.DefaultTask; +import org.gradle.api.file.FileCollection; import org.gradle.api.file.SourceDirectorySet; +import org.gradle.api.provider.PropertyState; +import org.gradle.api.provider.Provider; +import org.gradle.api.tasks.InputFiles; import org.gradle.api.tasks.JavaExec; import org.gradle.api.tasks.SourceSet; import org.gradle.api.tasks.SourceSetOutput; +import org.gradle.api.tasks.TaskAction; +import org.gradle.process.JavaExecSpec; /** * Custom {@link JavaExec} task for running a Spring Boot application. @@ -27,7 +38,34 @@ import org.gradle.api.tasks.SourceSetOutput; * @author Andy Wilkinson * @since 2.0.0 */ -public class BootRun extends JavaExec { +public class BootRun extends DefaultTask { + + private final PropertyState main = getProject().property(String.class); + + @SuppressWarnings("unchecked") + private final PropertyState> jvmArgs = (PropertyState>) (Object) getProject() + .property(List.class); + + @SuppressWarnings("unchecked") + private final PropertyState> args = (PropertyState>) (Object) getProject() + .property(List.class); + + private FileCollection classpath = getProject().files(); + + private List> execSpecConfigurers = new ArrayList<>(); + + /** + * Adds the given {@code entries} to the classpath used to run the application. + * @param entries the classpath entries + */ + public void classpath(Object... entries) { + this.classpath = this.classpath.plus(getProject().files(entries)); + } + + @InputFiles + public FileCollection getClasspath() { + return this.classpath; + } /** * Adds the {@link SourceDirectorySet#getSrcDirs() source directories} of the given @@ -38,18 +76,115 @@ public class BootRun extends JavaExec { * @param sourceSet the source set */ public void sourceResources(SourceSet sourceSet) { - setClasspath(getProject() - .files(sourceSet.getResources().getSrcDirs(), getClasspath()) - .filter((file) -> !file.equals(sourceSet.getOutput().getResourcesDir()))); + this.classpath = getProject() + .files(sourceSet.getResources().getSrcDirs(), this.classpath) + .filter((file) -> !file.equals(sourceSet.getOutput().getResourcesDir())); } - @Override - public void exec() { - if (System.console() != null) { - // Record that the console is available here for AnsiOutput to detect later - this.getEnvironment().put("spring.output.ansi.console-available", true); - } - super.exec(); + /** + * Returns the name of the main class to be run. + * @return the main class name or {@code null} + */ + public String getMain() { + return this.main.getOrNull(); + } + + /** + * Sets the main class to be executed using the given {@code mainProvider}. + * + * @param mainProvider provider of the main class name + */ + public void setMain(Provider mainProvider) { + this.main.set(mainProvider); + } + + /** + * Sets the main class to be run. + * + * @param main the main class name + */ + public void setMain(String main) { + this.main.set(main); + } + + /** + * Returns the JVM arguments to be used to run the application. + * @return the JVM arguments or {@code null} + */ + public List getJvmArgs() { + return this.jvmArgs.getOrNull(); + } + + /** + * Configures the application to be run using the JVM args provided by the given + * {@code jvmArgsProvider}. + * + * @param jvmArgsProvider the provider of the JVM args + */ + public void setJvmArgs(Provider> jvmArgsProvider) { + this.jvmArgs.set(jvmArgsProvider); + } + + /** + * Configures the application to be run using the given {@code jvmArgs}. + * @param jvmArgs the JVM args + */ + public void setJvmArgs(List jvmArgs) { + this.jvmArgs.set(jvmArgs); + } + + /** + * Returns the arguments to be used to run the application. + * @return the arguments or {@code null} + */ + public List getArgs() { + return this.args.getOrNull(); + } + + /** + * Configures the application to be run using the given {@code args}. + * @param args the args + */ + public void setArgs(List args) { + this.args.set(args); + } + + /** + * Configures the application to be run using the args provided by the given + * {@code argsProvider}. + * @param argsProvider the provider of the args + */ + public void setArgs(Provider> argsProvider) { + this.args.set(argsProvider); + } + + /** + * Registers the given {@code execSpecConfigurer} to be called to customize the + * {@link JavaExecSpec} prior to running the application. + * @param execSpecConfigurer the configurer + */ + public void execSpec(Action execSpecConfigurer) { + this.execSpecConfigurers.add(execSpecConfigurer); + } + + @TaskAction + public void run() { + getProject().javaexec((spec) -> { + spec.classpath(this.classpath); + spec.setMain(this.main.getOrNull()); + if (this.jvmArgs.isPresent()) { + spec.setJvmArgs(this.jvmArgs.get()); + } + if (this.args.isPresent()) { + spec.setArgs(this.args.get()); + } + if (System.console() != null) { + // Record that the console is available here for AnsiOutput to detect + // later + spec.environment("spring.output.ansi.console-available", true); + } + this.execSpecConfigurers.forEach((configurer) -> configurer.execute(spec)); + }); } } diff --git a/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/com/example/BootRunApplication.java b/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/com/example/BootRunApplication.java index 7d343e2a2d..623ef7dd32 100644 --- a/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/com/example/BootRunApplication.java +++ b/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/com/example/BootRunApplication.java @@ -18,6 +18,7 @@ package com.example; import java.io.File; import java.lang.management.ManagementFactory; +import java.util.Arrays; /** * Very basic application used for testing {@code BootRun}. @@ -31,6 +32,12 @@ public class BootRunApplication { } public static void main(String[] args) { + dumpClassPath(); + dumpArgs(args); + dumpJvmArgs(); + } + + private static void dumpClassPath() { int i = 1; for (String entry : ManagementFactory.getRuntimeMXBean().getClassPath() .split(File.pathSeparator)) { @@ -38,4 +45,12 @@ public class BootRunApplication { } } + private static void dumpArgs(String[] args) { + System.out.println(Arrays.toString(args)); + } + + private static void dumpJvmArgs() { + System.out.println(ManagementFactory.getRuntimeMXBean().getInputArguments()); + } + } diff --git a/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/docs/RunningDocumentationTests.java b/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/docs/RunningDocumentationTests.java index e999470102..05a6eb1dbc 100644 --- a/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/docs/RunningDocumentationTests.java +++ b/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/docs/RunningDocumentationTests.java @@ -59,4 +59,11 @@ public class RunningDocumentationTests { .contains(new File("src/main/resources").getPath()); } + @Test + public void bootRunExecSpecCustomization() throws IOException { + this.gradleBuild + .script("src/main/gradle/running/boot-run-custom-exec-spec.gradle") + .build(); + } + } diff --git a/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/run/BootRunIntegrationTests.java b/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/run/BootRunIntegrationTests.java index 45701cf1af..b12cbc8ccd 100644 --- a/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/run/BootRunIntegrationTests.java +++ b/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/run/BootRunIntegrationTests.java @@ -56,6 +56,33 @@ public class BootRunIntegrationTests { .doesNotContain(canonicalPathOf("src/main/resources")); } + @Test + public void argsCanBeConfigured() throws IOException { + copyApplication(); + new File(this.gradleBuild.getProjectDir(), "src/main/resources").mkdirs(); + BuildResult result = this.gradleBuild.build("bootRun"); + assertThat(result.task(":bootRun").getOutcome()).isEqualTo(TaskOutcome.SUCCESS); + assertThat(result.getOutput()).contains("--com.example.foo=bar"); + } + + @Test + public void jvmArgsCanBeConfigured() throws IOException { + copyApplication(); + new File(this.gradleBuild.getProjectDir(), "src/main/resources").mkdirs(); + BuildResult result = this.gradleBuild.build("bootRun"); + assertThat(result.task(":bootRun").getOutcome()).isEqualTo(TaskOutcome.SUCCESS); + assertThat(result.getOutput()).contains("-Dcom.example.foo=bar"); + } + + @Test + public void execSpecCanBeConfigured() throws IOException { + copyApplication(); + new File(this.gradleBuild.getProjectDir(), "src/main/resources").mkdirs(); + BuildResult result = this.gradleBuild.build("bootRun"); + assertThat(result.task(":bootRun").getOutcome()).isEqualTo(TaskOutcome.SUCCESS); + assertThat(result.getOutput()).contains("-Dcom.example.foo=bar"); + } + @Test public void sourceResourcesCanBeUsed() throws IOException { copyApplication(); diff --git a/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/run/BootRunIntegrationTests-argsCanBeConfigured.gradle b/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/run/BootRunIntegrationTests-argsCanBeConfigured.gradle new file mode 100644 index 0000000000..ef5b714f6e --- /dev/null +++ b/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/run/BootRunIntegrationTests-argsCanBeConfigured.gradle @@ -0,0 +1,12 @@ +buildscript { + dependencies { + classpath files(pluginClasspath.split(',')) + } +} + +apply plugin: 'java' +apply plugin: 'org.springframework.boot' + +bootRun { + args = ['--com.example.foo=bar'] +} diff --git a/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/run/BootRunIntegrationTests-execSpecCanBeConfigured.gradle b/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/run/BootRunIntegrationTests-execSpecCanBeConfigured.gradle new file mode 100644 index 0000000000..f7b3eacf5a --- /dev/null +++ b/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/run/BootRunIntegrationTests-execSpecCanBeConfigured.gradle @@ -0,0 +1,15 @@ +buildscript { + dependencies { + classpath files(pluginClasspath.split(',')) + } +} + +apply plugin: 'java' +apply plugin: 'org.springframework.boot' + + +bootRun { + execSpec { + systemProperty 'com.example.foo', 'bar' + } +} diff --git a/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/run/BootRunIntegrationTests-jvmArgsCanBeConfigured.gradle b/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/run/BootRunIntegrationTests-jvmArgsCanBeConfigured.gradle new file mode 100644 index 0000000000..9f2fd8ebfd --- /dev/null +++ b/spring-boot-tools/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/run/BootRunIntegrationTests-jvmArgsCanBeConfigured.gradle @@ -0,0 +1,12 @@ +buildscript { + dependencies { + classpath files(pluginClasspath.split(',')) + } +} + +apply plugin: 'java' +apply plugin: 'org.springframework.boot' + +bootRun { + jvmArgs = ['-Dcom.example.foo=bar'] +}