diff --git a/spring-boot-project/spring-boot-docs/src/main/asciidoc/deployment.adoc b/spring-boot-project/spring-boot-docs/src/main/asciidoc/deployment.adoc index 055505a945c..6bbae5b915b 100644 --- a/spring-boot-project/spring-boot-docs/src/main/asciidoc/deployment.adoc +++ b/spring-boot-project/spring-boot-docs/src/main/asciidoc/deployment.adoc @@ -678,69 +678,98 @@ Spring Boot Maven or Gradle plugins. The following property substitutions are supported with the default script: -[cols="1,6"] +[cols="1,3,3,3"] |=== -|Name |Description +|Name |Description |Gradle default |Maven default |`mode` -|The script mode. Defaults to `auto`. +|The script mode. +|`auto` +|`auto` |`initInfoProvides` -|The `Provides` section of "`INIT INFO`". Defaults to `spring-boot-application` for Gradle - and to `${project.artifactId}` for Maven. +|The `Provides` section of "`INIT INFO`" +|`${task.baseName}` +|`${project.artifactId}` |`initInfoRequiredStart` -|The `Required-Start` section of "`INIT INFO`". Defaults to `$remote_fs $syslog $network`. +|`Required-Start` section of "`INIT INFO`". +|`$remote_fs $syslog $network` +|`$remote_fs $syslog $network` |`initInfoRequiredStop` -|The `Required-Stop` section of "`INIT INFO`". Defaults to `$remote_fs $syslog $network`. - +|`Required-Stop` section of "`INIT INFO`". +|`$remote_fs $syslog $network` +|`$remote_fs $syslog $network` |`initInfoDefaultStart` -|The `Default-Start` section of "`INIT INFO`". Defaults to `2 3 4 5`. +|`Default-Start` section of "`INIT INFO`". +|`2 3 4 5` +|`2 3 4 5` |`initInfoDefaultStop` -|The `Default-Stop` section of "`INIT INFO`". Defaults to `0 1 6`. +|`Default-Stop` section of "`INIT INFO`". +|`0 1 6` +|`0 1 6` |`initInfoShortDescription` -|The `Short-Description` section of "`INIT INFO`". Defaults to `Spring Boot Application` -for Gradle and to `${project.name}` for Maven. +|`Short-Description` section of "`INIT INFO`". +|Single-line version of `${project.description}` (falling back to `${task.baseName}`) +|`${project.name}` |`initInfoDescription` -|The `Description` section of "`INIT INFO`". Defaults to `Spring Boot Application` for - Gradle and to `${project.description}` (falling back to `${project.name}`) for Maven. +|`Description` section of "`INIT INFO`". +|`${project.description}` (falling back to `${task.baseName}`) +|`${project.description}` (falling back to `${project.name}`) |`initInfoChkconfig` -|The `chkconfig` section of "`INIT INFO`". Defaults to `2345 99 01`. +|`chkconfig` section of "`INIT INFO`" +|`2345 99 01` +|`2345 99 01` |`confFolder` -|The default value for `CONF_FOLDER`. Defaults to the folder containing the jar. +|The default value for `CONF_FOLDER` +|Folder containing the jar +|Folder containing the jar |`inlinedConfScript` |Reference to a file script that should be inlined in the default launch script. This can be used to set environmental variables such as `JAVA_OPTS` before any external - config files are loaded. + config files are loaded +| +| |`logFolder` -|The default value for `LOG_FOLDER`. Only valid for an `init.d` service. +|Default value for `LOG_FOLDER`. Only valid for an `init.d` service +| +| |`logFilename` -|The default value for `LOG_FILENAME`. Only valid for an `init.d` service. +|Default value for `LOG_FILENAME`. Only valid for an `init.d` service +| +| |`pidFolder` -|The default value for `PID_FOLDER`. Only valid for an `init.d` service. +|Default value for `PID_FOLDER`. Only valid for an `init.d` service +| +| |`pidFilename` -|The default value for the name of the PID file in `PID_FOLDER`. Only valid for an - `init.d` service. +|Default value for the name of the PID file in `PID_FOLDER`. Only valid for an + `init.d` service +| +| |`useStartStopDaemon` |Whether the `start-stop-daemon` command, when it's available, should be used to control - the process. Defaults to `true`. + the process +|`true` +|`true` |`stopWaitTime` -|The default value for `STOP_WAIT_TIME`. Only valid for an `init.d` service. - Defaults to 60 seconds. +|Default value for `STOP_WAIT_TIME` in seconds. Only valid for an `init.d` service +|60 +|60 |=== diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootJar.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootJar.java index e63e83dd25b..d69e3f846ce 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootJar.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootJar.java @@ -184,7 +184,7 @@ public class BootJar extends Jar implements BootArchive { private LaunchScriptConfiguration enableLaunchScriptIfNecessary() { LaunchScriptConfiguration launchScript = this.support.getLaunchScript(); if (launchScript == null) { - launchScript = new LaunchScriptConfiguration(); + launchScript = new LaunchScriptConfiguration(this); this.support.setLaunchScript(launchScript); } return launchScript; diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootWar.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootWar.java index e6be12492f4..83c1dd8a2b1 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootWar.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootWar.java @@ -162,7 +162,7 @@ public class BootWar extends War implements BootArchive { private LaunchScriptConfiguration enableLaunchScriptIfNecessary() { LaunchScriptConfiguration launchScript = this.support.getLaunchScript(); if (launchScript == null) { - launchScript = new LaunchScriptConfiguration(); + launchScript = new LaunchScriptConfiguration(this); this.support.setLaunchScript(launchScript); } return launchScript; diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/LaunchScriptConfiguration.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/LaunchScriptConfiguration.java index 4152e2a8c14..6320939b627 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/LaunchScriptConfiguration.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/LaunchScriptConfiguration.java @@ -22,6 +22,9 @@ import java.io.Serializable; import java.util.HashMap; import java.util.Map; +import org.gradle.api.Project; +import org.gradle.api.tasks.bundling.AbstractArchiveTask; + import org.springframework.boot.loader.tools.FileUtils; /** @@ -37,6 +40,19 @@ public class LaunchScriptConfiguration implements Serializable { private File script; + public LaunchScriptConfiguration() { + + } + + LaunchScriptConfiguration(AbstractArchiveTask archiveTask) { + Project project = archiveTask.getProject(); + putIfMissing(this.properties, "initInfoProvides", archiveTask.getBaseName()); + putIfMissing(this.properties, "initInfoShortDescription", + removeLineBreaks(project.getDescription()), archiveTask.getBaseName()); + putIfMissing(this.properties, "initInfoDescription", + augmentLineBreaks(project.getDescription()), archiveTask.getBaseName()); + } + /** * Returns the properties that are applied to the launch script when it's being * including in the executable archive. @@ -121,4 +137,24 @@ public class LaunchScriptConfiguration implements Serializable { } } + private String removeLineBreaks(String string) { + return (string != null ? string.replaceAll("\\s+", " ") : null); + } + + private String augmentLineBreaks(String string) { + return (string != null ? string.replaceAll("\n", "\n# ") : null); + } + + private void putIfMissing(Map properties, String key, + String... valueCandidates) { + if (!properties.containsKey(key)) { + for (String candidate : valueCandidates) { + if (candidate != null && !candidate.isEmpty()) { + properties.put(key, candidate); + return; + } + } + } + } + } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/AbstractBootArchiveTests.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/AbstractBootArchiveTests.java index 978ce360782..f3e6c7a6d8c 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/AbstractBootArchiveTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/AbstractBootArchiveTests.java @@ -24,7 +24,9 @@ import java.nio.file.attribute.PosixFilePermission; import java.util.ArrayList; import java.util.Arrays; import java.util.Enumeration; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.jar.JarEntry; import java.util.jar.JarFile; @@ -64,6 +66,8 @@ public abstract class AbstractBootArchiveTests { private final String classesPath; + private Project project; + private T task; protected AbstractBootArchiveTests(Class taskClass, String launcherClass, @@ -77,10 +81,12 @@ public abstract class AbstractBootArchiveTests { @Before public void createTask() { try { - Project project = ProjectBuilder.builder() - .withProjectDir(this.temp.newFolder()).build(); + this.project = ProjectBuilder.builder().withProjectDir(this.temp.newFolder()) + .build(); + this.project + .setDescription("Test project for " + this.taskClass.getSimpleName()); this.task = configure( - project.getTasks().create("testArchive", this.taskClass)); + this.project.getTasks().create("testArchive", this.taskClass)); } catch (IOException ex) { throw new RuntimeException(ex); @@ -186,8 +192,12 @@ public abstract class AbstractBootArchiveTests { this.task.setMainClassName("com.example.Main"); this.task.launchScript(); this.task.execute(); + Map properties = new HashMap<>(); + properties.put("initInfoProvides", this.task.getBaseName()); + properties.put("initInfoShortDescription", this.project.getDescription()); + properties.put("initInfoDescription", this.project.getDescription()); assertThat(Files.readAllBytes(this.task.getArchivePath().toPath())) - .startsWith(new DefaultLaunchScript(null, null).toByteArray()); + .startsWith(new DefaultLaunchScript(null, properties).toByteArray()); try { Set permissions = Files .getPosixFilePermissions(this.task.getArchivePath().toPath()); @@ -211,13 +221,20 @@ public abstract class AbstractBootArchiveTests { } @Test - public void launchScriptPropertiesAreReplaced() throws IOException { + public void launchScriptInitInfoPropertiesCanBeCustomized() throws IOException { this.task.setMainClassName("com.example.Main"); - this.task.launchScript((configuration) -> configuration.getProperties() - .put("initInfoProvides", "test property value")); + this.task.launchScript((configuration) -> { + configuration.getProperties().put("initInfoProvides", "provides"); + configuration.getProperties().put("initInfoShortDescription", + "short description"); + configuration.getProperties().put("initInfoDescription", "description"); + }); this.task.execute(); - assertThat(Files.readAllBytes(this.task.getArchivePath().toPath())) - .containsSequence("test property value".getBytes()); + byte[] bytes = Files.readAllBytes(this.task.getArchivePath().toPath()); + assertThat(bytes).containsSequence("Provides: provides".getBytes()); + assertThat(bytes) + .containsSequence("Short-Description: short description".getBytes()); + assertThat(bytes).containsSequence("Description: description".getBytes()); } @Test diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/LaunchScriptConfigurationTests.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/LaunchScriptConfigurationTests.java new file mode 100644 index 00000000000..43fcbcd97d8 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/LaunchScriptConfigurationTests.java @@ -0,0 +1,93 @@ +/* + * Copyright 2012-2018 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.tasks.bundling; + +import org.gradle.api.Project; +import org.gradle.api.tasks.bundling.AbstractArchiveTask; +import org.junit.Before; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; + +/** + * Tests for {@link LaunchScriptConfiguration}. + * + * @author Andy Wilkinson + */ +public class LaunchScriptConfigurationTests { + + private final AbstractArchiveTask task = mock(AbstractArchiveTask.class); + + private final Project project = mock(Project.class); + + @Before + public void setUp() { + given(this.task.getProject()).willReturn(this.project); + } + + @Test + public void initInfoProvidesUsesArchiveBaseNameByDefault() { + given(this.task.getBaseName()).willReturn("base-name"); + assertThat(new LaunchScriptConfiguration(this.task).getProperties()) + .containsEntry("initInfoProvides", "base-name"); + } + + @Test + public void initInfoShortDescriptionUsesDescriptionByDefault() { + given(this.project.getDescription()).willReturn("Project description"); + assertThat(new LaunchScriptConfiguration(this.task).getProperties()) + .containsEntry("initInfoShortDescription", "Project description"); + } + + @Test + public void initInfoShortDescriptionUsesArchiveBaseNameWhenDescriptionIsNull() { + given(this.task.getBaseName()).willReturn("base-name"); + assertThat(new LaunchScriptConfiguration(this.task).getProperties()) + .containsEntry("initInfoShortDescription", "base-name"); + } + + @Test + public void initInfoShortDescriptionUsesSingleLineVersionOfMultiLineProjectDescription() { + given(this.project.getDescription()).willReturn("Project\ndescription"); + assertThat(new LaunchScriptConfiguration(this.task).getProperties()) + .containsEntry("initInfoShortDescription", "Project description"); + } + + @Test + public void initInfoDescriptionUsesArchiveBaseNameWhenDescriptionIsNull() { + given(this.task.getBaseName()).willReturn("base-name"); + assertThat(new LaunchScriptConfiguration(this.task).getProperties()) + .containsEntry("initInfoDescription", "base-name"); + } + + @Test + public void initInfoDescriptionUsesProjectDescriptionByDefault() { + given(this.project.getDescription()).willReturn("Project description"); + assertThat(new LaunchScriptConfiguration(this.task).getProperties()) + .containsEntry("initInfoDescription", "Project description"); + } + + @Test + public void initInfoDescriptionUsesCorrectlyFormattedMultiLineProjectDescription() { + given(this.project.getDescription()).willReturn("The\nproject\ndescription"); + assertThat(new LaunchScriptConfiguration(this.task).getProperties()) + .containsEntry("initInfoDescription", "The\n# project\n# description"); + } + +}