From 31febfa383df0da4f4c1afc7f9f093b14da9b0b7 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Mon, 13 Mar 2017 07:24:08 +0000 Subject: [PATCH] Create distribution for Boot jar or war when application plugin applied Closes gh-2622 --- .../ApplicationPluginFeatures.java | 107 +++++++++++ .../application/CreateBootStartScripts.java | 41 +++++ .../boot/gradle/plugin/SpringBootPlugin.java | 2 + .../src/main/resources/unixStartScript.txt | 172 ++++++++++++++++++ .../src/main/resources/windowsStartScript.txt | 85 +++++++++ 5 files changed, 407 insertions(+) create mode 100644 spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/application/ApplicationPluginFeatures.java create mode 100644 spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/application/CreateBootStartScripts.java create mode 100644 spring-boot-tools/spring-boot-gradle-plugin/src/main/resources/unixStartScript.txt create mode 100644 spring-boot-tools/spring-boot-gradle-plugin/src/main/resources/windowsStartScript.txt diff --git a/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/application/ApplicationPluginFeatures.java b/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/application/ApplicationPluginFeatures.java new file mode 100644 index 0000000000..2eadf41ab6 --- /dev/null +++ b/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/application/ApplicationPluginFeatures.java @@ -0,0 +1,107 @@ +/* + * Copyright 2012-2017 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.application; + +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.StringWriter; +import java.util.concurrent.Callable; + +import org.gradle.api.GradleException; +import org.gradle.api.Project; +import org.gradle.api.distribution.Distribution; +import org.gradle.api.distribution.DistributionContainer; +import org.gradle.api.file.CopySpec; +import org.gradle.api.file.FileCollection; +import org.gradle.api.plugins.ApplicationPlugin; +import org.gradle.api.plugins.ApplicationPluginConvention; +import org.gradle.jvm.application.scripts.TemplateBasedScriptGenerator; + +import org.springframework.boot.gradle.PluginFeatures; + +/** + * Features that are configured when the application plugin is applied. + * + * @author Andy Wilkinson + */ +public class ApplicationPluginFeatures implements PluginFeatures { + + @Override + public void apply(Project project) { + project.getPlugins().withType(ApplicationPlugin.class, + (plugin) -> configureDistribution(project)); + } + + public void configureDistribution(Project project) { + ApplicationPluginConvention applicationConvention = project.getConvention() + .getPlugin(ApplicationPluginConvention.class); + DistributionContainer distributions = project.getExtensions() + .getByType(DistributionContainer.class); + Distribution distribution = distributions.create("boot"); + CreateBootStartScripts bootStartScripts = project.getTasks() + .create("bootStartScripts", CreateBootStartScripts.class); + ((TemplateBasedScriptGenerator) bootStartScripts.getUnixStartScriptGenerator()) + .setTemplate(project.getResources().getText() + .fromString(loadResource("/unixStartScript.txt"))); + ((TemplateBasedScriptGenerator) bootStartScripts.getWindowsStartScriptGenerator()) + .setTemplate(project.getResources().getText() + .fromString(loadResource("/windowsStartScript.txt"))); + project.getConfigurations().all((configuration) -> { + if ("bootArchives".equals(configuration.getName())) { + distribution.getContents().with(project.copySpec().into("lib") + .from((Callable) () -> { + return configuration.getArtifacts().getFiles(); + })); + bootStartScripts.setClasspath(configuration.getArtifacts().getFiles()); + } + }); + bootStartScripts.getConventionMapping().map("outputDir", + () -> new File(project.getBuildDir(), "bootScripts")); + bootStartScripts.getConventionMapping().map("applicationName", + () -> applicationConvention.getApplicationName()); + CopySpec binCopySpec = project.copySpec().into("bin").from(bootStartScripts); + binCopySpec.setFileMode(0755); + distribution.getContents().with(binCopySpec); + } + + private String loadResource(String name) { + InputStreamReader reader = new InputStreamReader( + getClass().getResourceAsStream(name)); + char[] buffer = new char[4096]; + int read = 0; + StringWriter writer = new StringWriter(); + try { + while ((read = reader.read(buffer)) > 0) { + writer.write(buffer, 0, read); + } + return writer.toString(); + } + catch (IOException ex) { + throw new GradleException("Failed to read '" + name + "'", ex); + } + finally { + try { + reader.close(); + } + catch (IOException ex) { + // Continue + } + } + } + +} diff --git a/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/application/CreateBootStartScripts.java b/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/application/CreateBootStartScripts.java new file mode 100644 index 0000000000..9f11a5d51e --- /dev/null +++ b/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/application/CreateBootStartScripts.java @@ -0,0 +1,41 @@ +/* + * Copyright 2012-2017 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.application; + +import org.gradle.api.tasks.Optional; +import org.gradle.jvm.application.tasks.CreateStartScripts; + +/** + * Customization of {@link CreateStartScripts} that makes the {@link #getMainClassName() + * main class name} optional. + * + * @author Andy Wilkinson + */ +public class CreateBootStartScripts extends CreateStartScripts { + + @Override + @Optional + public String getMainClassName() { + return super.getMainClassName(); + } + + @Override + public void setMainClassName(String mainClassName) { + super.setMainClassName(mainClassName); + } + +} diff --git a/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/SpringBootPlugin.java b/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/SpringBootPlugin.java index 0879b8c3d1..7b1efcb8f4 100644 --- a/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/SpringBootPlugin.java +++ b/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/plugin/SpringBootPlugin.java @@ -24,6 +24,7 @@ import org.gradle.api.tasks.compile.JavaCompile; import org.springframework.boot.gradle.SpringBootPluginExtension; import org.springframework.boot.gradle.agent.AgentPluginFeatures; +import org.springframework.boot.gradle.application.ApplicationPluginFeatures; import org.springframework.boot.gradle.bundling.BundlingPluginFeatures; import org.springframework.boot.gradle.dependencymanagement.DependencyManagementPluginFeatures; import org.springframework.boot.gradle.run.RunPluginFeatures; @@ -42,6 +43,7 @@ public class SpringBootPlugin implements Plugin { project.getExtensions().create("springBoot", SpringBootPluginExtension.class, project); new AgentPluginFeatures().apply(project); + new ApplicationPluginFeatures().apply(project); new BundlingPluginFeatures().apply(project); new RunPluginFeatures().apply(project); new DependencyManagementPluginFeatures().apply(project); diff --git a/spring-boot-tools/spring-boot-gradle-plugin/src/main/resources/unixStartScript.txt b/spring-boot-tools/spring-boot-gradle-plugin/src/main/resources/unixStartScript.txt new file mode 100644 index 0000000000..7c3539c7c2 --- /dev/null +++ b/spring-boot-tools/spring-boot-gradle-plugin/src/main/resources/unixStartScript.txt @@ -0,0 +1,172 @@ +#!/usr/bin/env sh + +############################################################################## +## +## ${applicationName} start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: \$0 may be a link +PRG="\$0" +# Need this for relative symlinks. +while [ -h "\$PRG" ] ; do + ls=`ls -ld "\$PRG"` + link=`expr "\$ls" : '.*-> \\(.*\\)\$'` + if expr "\$link" : '/.*' > /dev/null; then + PRG="\$link" + else + PRG=`dirname "\$PRG"`"/\$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"\$PRG\"`/${appHomeRelativePath}" >/dev/null +APP_HOME="`pwd -P`" +cd "\$SAVED" >/dev/null + +APP_NAME="${applicationName}" +APP_BASE_NAME=`basename "\$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and ${optsEnvironmentVar} to pass JVM options to this script. +DEFAULT_JVM_OPTS=${defaultJvmOpts} + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "\$*" +} + +die ( ) { + echo + echo "\$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +JARPATH=$classpath + +# Determine the Java command to use to start the JVM. +if [ -n "\$JAVA_HOME" ] ; then + if [ -x "\$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="\$JAVA_HOME/jre/sh/java" + else + JAVACMD="\$JAVA_HOME/bin/java" + fi + if [ ! -x "\$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: \$JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "\$cygwin" = "false" -a "\$darwin" = "false" -a "\$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ \$? -eq 0 ] ; then + if [ "\$MAX_FD" = "maximum" -o "\$MAX_FD" = "max" ] ; then + MAX_FD="\$MAX_FD_LIMIT" + fi + ulimit -n \$MAX_FD + if [ \$? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: \$MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: \$MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if \$darwin; then + GRADLE_OPTS="\$GRADLE_OPTS \\"-Xdock:name=\$APP_NAME\\" \\"-Xdock:icon=\$APP_HOME/media/gradle.icns\\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if \$cygwin ; then + APP_HOME=`cygpath --path --mixed "\$APP_HOME"` + JARPATH=`cygpath --path --mixed "\$JARPATH"` + JAVACMD=`cygpath --unix "\$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in \$ROOTDIRSRAW ; do + ROOTDIRS="\$ROOTDIRS\$SEP\$dir" + SEP="|" + done + OURCYGPATTERN="(^(\$ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "\$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="\$OURCYGPATTERN|(\$GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "\$@" ; do + CHECK=`echo "\$arg"|egrep -c "\$OURCYGPATTERN" -` + CHECK2=`echo "\$arg"|egrep -c "^-"` ### Determine if an option + + if [ \$CHECK -ne 0 ] && [ \$CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args\$i`=`cygpath --path --ignore --mixed "\$arg"` + else + eval `echo args\$i`="\"\$arg\"" + fi + i=\$((i+1)) + done + case \$i in + (0) set -- ;; + (1) set -- "\$args0" ;; + (2) set -- "\$args0" "\$args1" ;; + (3) set -- "\$args0" "\$args1" "\$args2" ;; + (4) set -- "\$args0" "\$args1" "\$args2" "\$args3" ;; + (5) set -- "\$args0" "\$args1" "\$args2" "\$args3" "\$args4" ;; + (6) set -- "\$args0" "\$args1" "\$args2" "\$args3" "\$args4" "\$args5" ;; + (7) set -- "\$args0" "\$args1" "\$args2" "\$args3" "\$args4" "\$args5" "\$args6" ;; + (8) set -- "\$args0" "\$args1" "\$args2" "\$args3" "\$args4" "\$args5" "\$args6" "\$args7" ;; + (9) set -- "\$args0" "\$args1" "\$args2" "\$args3" "\$args4" "\$args5" "\$args6" "\$args7" "\$args8" ;; + esac +fi + +# Escape application args +save ( ) { + for i do printf %s\\\\n "\$i" | sed "s/'/'\\\\\\\\''/g;1s/^/'/;\\\$s/\\\$/' \\\\\\\\/" ; done + echo " " +} +APP_ARGS=\$(save "\$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- \$DEFAULT_JVM_OPTS \$JAVA_OPTS \$${optsEnvironmentVar} <% if ( appNameSystemProperty ) { %>"\"-D${appNameSystemProperty}=\$APP_BASE_NAME\"" <% } %>-jar "\"\$JARPATH\"" "\$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "\$(uname)" = "Darwin" ] && [ "\$HOME" = "\$PWD" ]; then + cd "\$(dirname "\$0")" +fi + +exec "\$JAVACMD" "\$@" diff --git a/spring-boot-tools/spring-boot-gradle-plugin/src/main/resources/windowsStartScript.txt b/spring-boot-tools/spring-boot-gradle-plugin/src/main/resources/windowsStartScript.txt new file mode 100644 index 0000000000..58f2a3d59c --- /dev/null +++ b/spring-boot-tools/spring-boot-gradle-plugin/src/main/resources/windowsStartScript.txt @@ -0,0 +1,85 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem ${applicationName} startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=.\ + +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME%${appHomeRelativePath} + +@rem Add default JVM options here. You can also use JAVA_OPTS and ${optsEnvironmentVar} to pass JVM options to this script. +set DEFAULT_JVM_OPTS=${defaultJvmOpts} + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set JARPATH=$classpath + +@rem Execute ${applicationName} +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %${optsEnvironmentVar}% <% if ( appNameSystemProperty ) { %>"-D${appNameSystemProperty}=%APP_BASE_NAME%"<% } %> -jar "%JARPATH%" %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable ${exitEnvironmentVar} if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%${exitEnvironmentVar}%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega