Merge branch 'gh-1117'
This commit is contained in:
commit
ba2ea301f1
|
|
@ -383,6 +383,18 @@ want the other Boot features but not this one)
|
|||
|`customConfiguration`
|
||||
|The name of the custom configuration which is used to populate the nested lib directory
|
||||
(without specifying this you get all compile and runtime dependencies).
|
||||
|
||||
|`executable`
|
||||
|Boolean flag to indicate if jar files are fully executable on Unix like operating
|
||||
systems. Defaults to `true`.
|
||||
|
||||
|`embeddedLaunchScript`
|
||||
|The embedded launch script to prepend to the front of the jar if it is fully executable.
|
||||
If not specified the 'Spring Boot' default script will be used.
|
||||
|
||||
|`embeddedLaunchScriptProperties`
|
||||
|Additional properties that to be expanded in the launch script. The default script
|
||||
supports a `mode` property which can contain the values `auto`, `service` or `run`.
|
||||
|===
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,20 @@
|
|||
[[cloud-deployment]]
|
||||
= Deploying to the cloud
|
||||
[[deployment]]
|
||||
== Deploying Spring Boot applications
|
||||
|
||||
[partintro]
|
||||
--
|
||||
Spring Boot's flexible packaging options provide a great deal of choice when it comes to
|
||||
deploying your application. You can easily deploy Spring Boot applications to a variety
|
||||
of cloud platforms, to a container images (such as Docker) or to virtual/real machines.
|
||||
|
||||
This section covers some of the more common deployment scenarios.
|
||||
--
|
||||
|
||||
|
||||
|
||||
[[cloud-deployment]]
|
||||
== Deploying to the cloud
|
||||
|
||||
Spring Boot's executable jars are ready-made for most popular cloud PaaS
|
||||
(platform-as-a-service) providers. These providers tend to require that you
|
||||
"`bring your own container`"; they manage application processes (not Java applications
|
||||
|
|
@ -23,12 +35,11 @@ to run packaged within it.
|
|||
In this section we'll look at what it takes to get the
|
||||
<<getting-started.adoc#getting-started-first-application, simple application that we
|
||||
developed>> in the "`Getting Started`" section up and running in the Cloud.
|
||||
--
|
||||
|
||||
|
||||
|
||||
[[cloud-deployment-cloud-foundry]]
|
||||
== Cloud Foundry
|
||||
=== Cloud Foundry
|
||||
Cloud Foundry provides default buildpacks that come into play if no other buildpack is
|
||||
specified. The Cloud Foundry https://github.com/cloudfoundry/java-buildpack[Java buildpack]
|
||||
has excellent support for Spring applications, including Spring Boot. You can deploy
|
||||
|
|
@ -102,7 +113,7 @@ able to hit the application at the URI given, in this case
|
|||
|
||||
|
||||
[[cloud-deployment-cloud-foundry-services]]
|
||||
=== Binding to services
|
||||
==== Binding to services
|
||||
By default, metadata about the running application as well as service connection
|
||||
information is exposed to the application as environment variables (for example:
|
||||
`$VCAP_SERVICES`). This architecture decision is due to Cloud Foundry's polyglot
|
||||
|
|
@ -142,7 +153,7 @@ auto-configuration support and a `spring-boot-starter-cloud-connectors` starter
|
|||
|
||||
|
||||
[[cloud-deployment-heroku]]
|
||||
== Heroku
|
||||
=== Heroku
|
||||
Heroku is another popular PaaS platform. To customize Heroku builds, you provide a
|
||||
`Procfile`, which provides the incantation required to deploy an application. Heroku
|
||||
assigns a `port` for the Java application to use and then ensures that routing to the
|
||||
|
|
@ -225,7 +236,7 @@ Your application should now be up and running on Heroku.
|
|||
|
||||
|
||||
[[cloud-deployment-openshift]]
|
||||
== Openshift
|
||||
=== Openshift
|
||||
https://www.openshift.com/[Openshift] is the RedHat public (and enterprise) PaaS solution.
|
||||
Like Heroku, it works by running scripts triggered by git commits, so you can script
|
||||
the launching of a Spring Boot application in pretty much any way you like as long as the
|
||||
|
|
@ -288,14 +299,85 @@ run the app.
|
|||
|
||||
|
||||
[[cloud-deployment-gae]]
|
||||
== Google App Engine
|
||||
=== Google App Engine
|
||||
Google App Engine is tied to the Servlet 2.5 API, so you can't deploy a Spring Application
|
||||
there without some modifications. See the <<howto.adoc#howto-servlet-2-5, Servlet 2.5 section>>
|
||||
of this guide.
|
||||
|
||||
|
||||
[[deployment-service]]
|
||||
== Installing Spring Boot applications
|
||||
In additional to running Spring Boot applications using `java -jar` it is also possible
|
||||
to execute applications directly on Unix systems (Linux, OSX, FreeBSD etc). This makes it
|
||||
very easy to install and manage Spring Boot applications in common production
|
||||
environments. As long as you are generating '`fully executable`' jars from your build, and
|
||||
you are not using a custom `embeddedLaunchScript`, the following techniques can be used.
|
||||
|
||||
[[cloud-deployment-whats-next]]
|
||||
|
||||
|
||||
=== Unix/Linux services
|
||||
Spring Boot application can be easily started as Unix/Linux services using either `init.d`
|
||||
or `systemd`.
|
||||
|
||||
|
||||
|
||||
==== Installation as a init.d (system v) service
|
||||
The default executable script that is embedded into Spring Boot executable jars will act
|
||||
as an `init.d` script when it is symlinked to `/etc/init.d`. The standard `start`, `stop`,
|
||||
`restart` and `status` commands can be used. The script supports the following features:
|
||||
|
||||
* Starts the services as the user that owns the jar file
|
||||
* Tracks application PIDs using `/var/run/<appname>.pid`
|
||||
* Writes console logs to `/var/log/<appname>.log`
|
||||
|
||||
Assuming that you have a Spring Boot application installed in `/var/myapp`, to install a
|
||||
Spring Boot application as an `init.d` service simply create a symlink:
|
||||
|
||||
[indent=0,subs="verbatim,quotes,attributes"]
|
||||
----
|
||||
$ sudo link -s /var/myapp/myapp.jar /etc/init.d/myapp
|
||||
----
|
||||
|
||||
TIP: It is advisable to create a specific user account to run you application. Ensure
|
||||
that you have set the owner of the jar file using `chown` before installing your service.
|
||||
|
||||
Once installed, you can start and stop the service in the usual way. You can also flag the
|
||||
application to start automatically using your standard operating system tools. For example,
|
||||
if you use Debian:
|
||||
|
||||
[indent=0,subs="verbatim,quotes,attributes"]
|
||||
----
|
||||
$ update-rc.d myapp defaults <priority>
|
||||
----
|
||||
|
||||
|
||||
|
||||
==== Installation as a systemd service
|
||||
Systemd is the successor to `init.d` scripts, and now being used by many many modern Linux
|
||||
distributions. Although you can continue to use `init.d` script with `systemd`, it is also
|
||||
possible to launch Spring Boot applications using `systemd` '`service`' scripts.
|
||||
|
||||
For example, to run a Spring Boot application installed in `var/myapp` you can add the
|
||||
following script in `/etc/systemd/system/myapp.service`:
|
||||
|
||||
[indent=0]
|
||||
----
|
||||
[Unit]
|
||||
Description=myapp
|
||||
After=syslog.target
|
||||
|
||||
[Service]
|
||||
ExecStart=/var/myapp/myapp.jar
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
----
|
||||
|
||||
TIP: Remember to change the `Description` and `ExecStart` fields for your application.
|
||||
|
||||
|
||||
|
||||
[[deployment-whats-next]]
|
||||
== What to read next
|
||||
Check out the http://www.cloudfoundry.com/[Cloud Foundry], https://www.heroku.com/[Heroku]
|
||||
and https://www.openshift.com[Openshift] web sites for more information about the kinds of
|
||||
|
|
@ -307,6 +389,3 @@ The next section goes on to cover the _<<spring-boot-cli.adoc#cli, Spring Boot C
|
|||
or you can jump ahead to read about
|
||||
_<<build-tool-plugins.adoc#build-tool-plugins, build tool plugins>>_.
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -136,10 +136,9 @@ When you're ready to push your Spring Boot application to production, we've got
|
|||
== Advanced topics
|
||||
Lastly, we have a few topics for the more advanced user.
|
||||
|
||||
* *Deploy to the cloud:*
|
||||
<<cloud-deployment.adoc#cloud-deployment-cloud-foundry, Cloud Foundry>> |
|
||||
<<cloud-deployment.adoc#cloud-deployment-heroku, Heroku>> |
|
||||
<<cloud-deployment.adoc#cloud-deployment-cloudbees, CloudBees>>
|
||||
* *Deploy Spring Boot Applications:*
|
||||
<<deployment.adoc#cloud-deployment, Cloud Deployment>> |
|
||||
<<deployment.adoc#deployment-service, OS Service>>
|
||||
* *Build tool plugins:*
|
||||
<<build-tool-plugins.adoc#build-tool-plugins-maven-plugin, Maven>> |
|
||||
<<build-tool-plugins.adoc#build-tool-plugins-gradle-plugin, Gradle>>
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ include::getting-started.adoc[]
|
|||
include::using-spring-boot.adoc[]
|
||||
include::spring-boot-features.adoc[]
|
||||
include::production-ready-features.adoc[]
|
||||
include::cloud-deployment.adoc[]
|
||||
include::deployment.adoc[]
|
||||
include::spring-boot-cli.adoc[]
|
||||
include::build-tool-plugins.adoc[]
|
||||
include::howto.adoc[]
|
||||
|
|
|
|||
|
|
@ -1043,7 +1043,7 @@ If you want to explore some of the concepts discussed in this chapter, you can t
|
|||
look at the actuator {github-code}/spring-boot-samples[sample applications]. You also
|
||||
might want to read about graphing tools such as http://graphite.wikidot.com/[Graphite].
|
||||
|
||||
Otherwise, you can continue on, to read about <<cloud-deployment.adoc#cloud-deployment,
|
||||
'`cloud deployment options`'>> or jump ahead
|
||||
Otherwise, you can continue on, to read about <<deployment.adoc#deployment,
|
||||
'`deployment options`'>> or jump ahead
|
||||
for some in-depth information about Spring Boot's
|
||||
_<<build-tool-plugins.adoc#build-tool-plugins, build tool plugins>>_.
|
||||
|
|
|
|||
|
|
@ -5,8 +5,8 @@
|
|||
[partintro]
|
||||
--
|
||||
This section goes into more detail about how you should use Spring Boot. It covers topics
|
||||
such as build systems, auto-configuration and run/deployment options. We also cover some
|
||||
Spring Boot best practices. Although there is nothing particularly special about
|
||||
such as build systems, auto-configuration and how to run your applications. We also cover
|
||||
some Spring Boot best practices. Although there is nothing particularly special about
|
||||
Spring Boot (it is just another library that you can consume), there are a few
|
||||
recommendations that, when followed, will make your development process just a
|
||||
little easier.
|
||||
|
|
|
|||
|
|
@ -16,6 +16,9 @@
|
|||
|
||||
package org.springframework.boot.gradle
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.boot.loader.tools.Layout
|
||||
import org.springframework.boot.loader.tools.Layouts
|
||||
|
||||
|
|
@ -130,4 +133,21 @@ public class SpringBootPluginExtension {
|
|||
*/
|
||||
boolean applyExcludeRules = true;
|
||||
|
||||
/**
|
||||
* If a fully executable jar (for *nix machines) should be generated by prepending a
|
||||
* launch script to the jar.
|
||||
*/
|
||||
boolean executable = true;
|
||||
|
||||
/**
|
||||
* The embedded launch script to prepend to the front of the jar if it is fully
|
||||
* executable. If not specified the 'Spring Boot' default script will be used.
|
||||
*/
|
||||
File embeddedLaunchScript;
|
||||
|
||||
/**
|
||||
* Properties that should be expanded in the embedded launch script.
|
||||
*/
|
||||
Map<String,String> embeddedLaunchScriptProperties;
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,6 +28,8 @@ import org.gradle.api.Project;
|
|||
import org.gradle.api.tasks.TaskAction;
|
||||
import org.gradle.api.tasks.bundling.Jar;
|
||||
import org.springframework.boot.gradle.SpringBootPluginExtension;
|
||||
import org.springframework.boot.loader.tools.DefaultLaunchScript;
|
||||
import org.springframework.boot.loader.tools.LaunchScript;
|
||||
import org.springframework.boot.loader.tools.Repackager;
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
|
||||
|
|
@ -80,6 +82,10 @@ public class RepackageTask extends DefaultTask {
|
|||
this.classifier = classifier;
|
||||
}
|
||||
|
||||
void setOutputFile(File file) {
|
||||
this.outputFile = file;
|
||||
}
|
||||
|
||||
@TaskAction
|
||||
public void repackage() {
|
||||
Project project = getProject();
|
||||
|
|
@ -170,7 +176,8 @@ public class RepackageTask extends DefaultTask {
|
|||
}
|
||||
repackager.setBackupSource(this.extension.isBackupSource());
|
||||
try {
|
||||
repackager.repackage(file, this.libraries);
|
||||
LaunchScript launchScript = getLaunchScript();
|
||||
repackager.repackage(file, this.libraries, launchScript);
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new IllegalStateException(ex.getMessage(), ex);
|
||||
|
|
@ -201,6 +208,15 @@ public class RepackageTask extends DefaultTask {
|
|||
getLogger().info("Setting mainClass: " + mainClass);
|
||||
repackager.setMainClass(mainClass);
|
||||
}
|
||||
|
||||
private LaunchScript getLaunchScript() throws IOException {
|
||||
if (this.extension.isExecutable()) {
|
||||
return new DefaultLaunchScript(this.extension.getEmbeddedLaunchScript(),
|
||||
this.extension.getEmbeddedLaunchScriptProperties());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -228,10 +244,7 @@ public class RepackageTask extends DefaultTask {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void setOutputFile(File file) {
|
||||
this.outputFile = file;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,111 @@
|
|||
/*
|
||||
* Copyright 2012-2015 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.loader.tools;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Default implementation of {@link LaunchScript}. Provides the default Spring Boot launch
|
||||
* script or can load a specific script File. Also support mustache style template
|
||||
* expansion of the form <code>{{name:default}}</code>.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @since 1.3.0
|
||||
*/
|
||||
public class DefaultLaunchScript implements LaunchScript {
|
||||
|
||||
private static final Charset UTF_8 = Charset.forName("UTF-8");
|
||||
|
||||
private static final int BUFFER_SIZE = 4096;
|
||||
|
||||
private static final Pattern PLACEHOLDER_PATTERN = Pattern
|
||||
.compile("\\{\\{(\\w+)(:.*?)?\\}\\}");
|
||||
|
||||
private final String content;
|
||||
|
||||
/**
|
||||
* Create a new {@link DefaultLaunchScript} instance.
|
||||
* @param file the source script file or {@code null} to use the default
|
||||
* @param properties an optional set of script properties used for variable expansion
|
||||
* @throws IOException if the script cannot be loaded
|
||||
*/
|
||||
public DefaultLaunchScript(File file, Map<?, ?> properties) throws IOException {
|
||||
String content = loadContent(file);
|
||||
this.content = expandPlaceholders(content, properties);
|
||||
}
|
||||
|
||||
private String loadContent(File file) throws IOException {
|
||||
if (file == null) {
|
||||
return loadContent(getClass().getResourceAsStream("launch.script"));
|
||||
}
|
||||
return loadContent(new FileInputStream(file));
|
||||
}
|
||||
|
||||
private String loadContent(InputStream inputStream) throws IOException {
|
||||
try {
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
copy(inputStream, outputStream);
|
||||
return new String(outputStream.toByteArray(), UTF_8);
|
||||
}
|
||||
finally {
|
||||
inputStream.close();
|
||||
}
|
||||
}
|
||||
|
||||
private void copy(InputStream inputStream, OutputStream outputStream)
|
||||
throws IOException {
|
||||
byte[] buffer = new byte[BUFFER_SIZE];
|
||||
int bytesRead = -1;
|
||||
while ((bytesRead = inputStream.read(buffer)) != -1) {
|
||||
outputStream.write(buffer, 0, bytesRead);
|
||||
}
|
||||
outputStream.flush();
|
||||
}
|
||||
|
||||
private String expandPlaceholders(String content, Map<?, ?> properties) {
|
||||
StringBuffer expanded = new StringBuffer();
|
||||
Matcher matcher = PLACEHOLDER_PATTERN.matcher(content);
|
||||
while (matcher.find()) {
|
||||
String name = matcher.group(1);
|
||||
String value = matcher.group(2);
|
||||
if (properties != null && properties.containsKey(name)) {
|
||||
value = (String) properties.get(name);
|
||||
}
|
||||
else {
|
||||
value = (value == null ? matcher.group(0) : value.substring(1));
|
||||
}
|
||||
matcher.appendReplacement(expanded, value);
|
||||
}
|
||||
matcher.appendTail(expanded);
|
||||
return expanded.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] toByteArray() {
|
||||
return this.content.getBytes(UTF_8);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -27,6 +27,9 @@ import java.io.IOException;
|
|||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.attribute.PosixFilePermission;
|
||||
import java.util.Arrays;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashSet;
|
||||
|
|
@ -63,7 +66,37 @@ public class JarWriter {
|
|||
* @throws FileNotFoundException
|
||||
*/
|
||||
public JarWriter(File file) throws FileNotFoundException, IOException {
|
||||
this.jarOutput = new JarOutputStream(new FileOutputStream(file));
|
||||
this(file, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link JarWriter} instance.
|
||||
* @param file the file to write
|
||||
* @param launchScript an optional launch script to prepend to the front of the jar
|
||||
* @throws IOException
|
||||
* @throws FileNotFoundException
|
||||
*/
|
||||
public JarWriter(File file, LaunchScript launchScript) throws FileNotFoundException,
|
||||
IOException {
|
||||
FileOutputStream fileOutputStream = new FileOutputStream(file);
|
||||
if (launchScript != null) {
|
||||
fileOutputStream.write(launchScript.toByteArray());
|
||||
setExecutableFilePermission(file);
|
||||
}
|
||||
this.jarOutput = new JarOutputStream(fileOutputStream);
|
||||
}
|
||||
|
||||
private void setExecutableFilePermission(File file) {
|
||||
try {
|
||||
Path path = file.toPath();
|
||||
Set<PosixFilePermission> permissions = new HashSet<PosixFilePermission>(
|
||||
Files.getPosixFilePermissions(path));
|
||||
permissions.add(PosixFilePermission.OWNER_EXECUTE);
|
||||
Files.setPosixFilePermissions(path, permissions);
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
// Ignore and continue creating the jar
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* Copyright 2012-2015 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.loader.tools;
|
||||
|
||||
/**
|
||||
* A script that can be prepended to the front of a JAR file to make it executable.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @since 1.3.0
|
||||
*/
|
||||
public interface LaunchScript {
|
||||
|
||||
/**
|
||||
* The the content of the launch script as a byte array.
|
||||
* @return the script bytes
|
||||
*/
|
||||
byte[] toByteArray();
|
||||
|
||||
}
|
||||
|
|
@ -104,17 +104,29 @@ public class Repackager {
|
|||
* @throws IOException
|
||||
*/
|
||||
public void repackage(File destination, Libraries libraries) throws IOException {
|
||||
repackage(destination, libraries, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Repackage to the given destination so that it can be launched using '
|
||||
* {@literal java -jar}'
|
||||
* @param destination the destination file (may be the same as the source)
|
||||
* @param libraries the libraries required to run the archive
|
||||
* @param launchScript an optional launch script prepended to the front of the jar
|
||||
* @throws IOException
|
||||
* @since 1.3.0
|
||||
*/
|
||||
public void repackage(File destination, Libraries libraries, LaunchScript launchScript)
|
||||
throws IOException {
|
||||
if (destination == null || destination.isDirectory()) {
|
||||
throw new IllegalArgumentException("Invalid destination");
|
||||
}
|
||||
if (libraries == null) {
|
||||
throw new IllegalArgumentException("Libraries must not be null");
|
||||
}
|
||||
|
||||
if (alreadyRepackaged()) {
|
||||
return;
|
||||
}
|
||||
|
||||
destination = destination.getAbsoluteFile();
|
||||
File workingSource = this.source;
|
||||
if (this.source.equals(destination)) {
|
||||
|
|
@ -127,7 +139,7 @@ public class Repackager {
|
|||
try {
|
||||
JarFile jarFileSource = new JarFile(workingSource);
|
||||
try {
|
||||
repackage(jarFileSource, destination, libraries);
|
||||
repackage(jarFileSource, destination, libraries, launchScript);
|
||||
}
|
||||
finally {
|
||||
jarFileSource.close();
|
||||
|
|
@ -152,9 +164,9 @@ public class Repackager {
|
|||
}
|
||||
}
|
||||
|
||||
private void repackage(JarFile sourceJar, File destination, Libraries libraries)
|
||||
throws IOException {
|
||||
final JarWriter writer = new JarWriter(destination);
|
||||
private void repackage(JarFile sourceJar, File destination, Libraries libraries,
|
||||
LaunchScript launchScript) throws IOException {
|
||||
final JarWriter writer = new JarWriter(destination, launchScript);
|
||||
try {
|
||||
final Set<String> seen = new HashSet<String>();
|
||||
writer.writeManifest(buildManifest(sourceJar));
|
||||
|
|
|
|||
|
|
@ -0,0 +1,164 @@
|
|||
#!/bin/bash
|
||||
#
|
||||
# . ____ _ __ _ _
|
||||
# /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
|
||||
# ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
|
||||
# \\/ ___)| |_)| | | | | || (_| | ) ) ) )
|
||||
# ' |____| .__|_| |_|_| |_\__, | / / / /
|
||||
# =========|_|==============|___/=/_/_/_/
|
||||
# :: Spring Boot Startup Script ::
|
||||
#
|
||||
|
||||
WORKING_DIR="$(pwd)"
|
||||
PID_FOLDER="/var/run"
|
||||
USER_PID_FOLDER="/tmp"
|
||||
LOG_FOLDER="/var/log"
|
||||
USER_LOG_FOLDER="/tmp"
|
||||
|
||||
# Setup defaults
|
||||
[[ -z "$mode" ]] && mode="{{mode:auto}}" # modes are "auto", "service" or "run"
|
||||
|
||||
# ANSI Colors
|
||||
echoRed() { echo $'\e[0;31m'$1$'\e[0m'; }
|
||||
echoGreen() { echo $'\e[0;32m'$1$'\e[0m'; }
|
||||
echoYellow() { echo $'\e[0;33m'$1$'\e[0m'; }
|
||||
|
||||
# Follow symlinks to find the real jar and detect init.d script
|
||||
cd $(dirname "$0")
|
||||
[[ -z "$jarfile" ]] && jarfile=$(pwd)/$(basename "$0")
|
||||
while [[ -L "$jarfile" ]]; do
|
||||
[[ "$jarfile" =~ "init.d" ]] && init_script=$(basename "$jarfile")
|
||||
jarfile=$(readlink "$jarfile")
|
||||
cd $(dirname "$jarfile")
|
||||
jarfile=$(pwd)/$(basename "$jarfile")
|
||||
done
|
||||
cd "$WORKING_DIR"
|
||||
|
||||
# Determine the script mode
|
||||
action="run"
|
||||
if [[ "$mode" == "auto" && -n "$init_script" ]] || [[ "$mode" == "service" ]]; then
|
||||
action="$1"
|
||||
shift
|
||||
fi
|
||||
|
||||
# Create an identity for log/pid files
|
||||
if [[ -n "$init_script" ]]; then
|
||||
identity="${init_script}"
|
||||
else
|
||||
jar_folder=$(dirname "$jarfile")
|
||||
identity=$(basename "${jarfile%.*}")_${jar_folder//\//}
|
||||
fi
|
||||
|
||||
# Build the pid and log filenames
|
||||
if [[ -n "$init_script" ]]; then
|
||||
pid_file="$PID_FOLDER/${identity}/${identity}.pid"
|
||||
log_file="$LOG_FOLDER/${identity}.log"
|
||||
else
|
||||
pid_file="$USER_PID_FOLDER/${identity}.pid"
|
||||
log_file="$USER_LOG_FOLDER/${identity}.log"
|
||||
fi
|
||||
|
||||
# Determine the user to run as
|
||||
[[ $(id -u) == "0" ]] && run_user=$(ls -ld "$jarfile" | awk '{print $3}')
|
||||
|
||||
# Find Java
|
||||
if type -p java 2>&1> /dev/null; then
|
||||
javaexe=java
|
||||
elif [[ -n "$JAVA_HOME" ]] && [[ -x "$JAVA_HOME/bin/java" ]]; then
|
||||
javaexe="$JAVA_HOME/bin/java"
|
||||
elif [[ -x "/usr/bin/java" ]]; then
|
||||
javaexe="/usr/bin/java"
|
||||
else
|
||||
echo "Unable to find Java"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Build actual command to execute
|
||||
command="$javaexe -jar -Dsun.misc.URLClassPath.disableJarChecking=true $jarfile $@"
|
||||
|
||||
# Utility functions
|
||||
checkPermissions() {
|
||||
touch "$pid_file" &> /dev/null || { echoRed "Operation not permitted (cannot access pid file)"; exit 1; }
|
||||
touch "$log_file" &> /dev/null || { echoRed "Operation not permitted (cannot access log file)"; exit 1; }
|
||||
}
|
||||
|
||||
isRunning() {
|
||||
ps -p $1 &> /dev/null
|
||||
}
|
||||
|
||||
# Action functions
|
||||
start() {
|
||||
if [[ -f "$pid_file" ]]; then
|
||||
pid=$(cat "$pid_file")
|
||||
isRunning $pid && { echoYellow "Already running [$pid]"; exit 0; }
|
||||
fi
|
||||
pushd $(dirname "$jarfile") > /dev/null
|
||||
if [[ -n "$run_user" ]]; then
|
||||
mkdir "$PID_FOLDER/${identity}" &> /dev/null
|
||||
checkPermissions
|
||||
chown "$run_user" "$PID_FOLDER/${identity}"
|
||||
chown "$run_user" "$pid_file"
|
||||
chown "$run_user" "$log_file"
|
||||
su -c "$command &> \"$log_file\" & echo \$!" $run_user > "$pid_file"
|
||||
pid=$(cat "$pid_file")
|
||||
else
|
||||
checkPermissions
|
||||
$command &> "$log_file" &
|
||||
pid=$!
|
||||
disown $pid
|
||||
echo "$pid" > "$pid_file"
|
||||
fi
|
||||
[[ -z $pid ]] && { echoRed "Failed to start"; exit 1; }
|
||||
echoGreen "Started [$pid]"
|
||||
}
|
||||
|
||||
stop() {
|
||||
[[ -f $pid_file ]] || { echoRed "Not running (pidfile not found)"; exit 1; }
|
||||
pid=$(cat "$pid_file")
|
||||
isRunning $pid || { echoRed "Not running (process ${pid} not found)"; exit 1; }
|
||||
kill -HUP $pid &> /dev/null || { echoRed "Unable to kill process ${pid}"; exit 1; }
|
||||
for i in $(seq 1 20); do
|
||||
isRunning ${pid} || { echoGreen "Stopped [$pid]"; rm -f $pid_file; exit 0; }
|
||||
sleep 1
|
||||
done
|
||||
echoRed "Unable to kill process ${pid}";
|
||||
exit 3;
|
||||
}
|
||||
|
||||
restart() {
|
||||
stop
|
||||
start
|
||||
}
|
||||
|
||||
status() {
|
||||
[[ -f $pid_file ]] || { echoRed "Not running"; exit 1; }
|
||||
pid=$(cat "$pid_file")
|
||||
isRunning $pid || { echoRed "Not running (process ${pid} not found)"; exit 1; }
|
||||
echoGreen "Running [$pid]"
|
||||
exit 0
|
||||
}
|
||||
|
||||
run() {
|
||||
pushd $(dirname "$jarfile") > /dev/null
|
||||
exec $command
|
||||
popd
|
||||
}
|
||||
|
||||
# Call the appropriate action function
|
||||
case "$action" in
|
||||
start)
|
||||
start "$@";;
|
||||
stop)
|
||||
stop "$@";;
|
||||
restart)
|
||||
restart "$@";;
|
||||
status)
|
||||
status "$@";;
|
||||
run)
|
||||
run "$@";;
|
||||
*)
|
||||
echo "Usage: $0 {start|stop|restart|status|run}"; exit 1;
|
||||
esac
|
||||
|
||||
exit 0
|
||||
|
||||
|
|
@ -0,0 +1,111 @@
|
|||
/*
|
||||
* Copyright 2012-2015 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.loader.tools;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Properties;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link DefaultLaunchScript}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class DefaultLaunchScriptTests {
|
||||
|
||||
@Rule
|
||||
public TemporaryFolder temporaryFolder = new TemporaryFolder();
|
||||
|
||||
@Test
|
||||
public void loadsDefaultScript() throws Exception {
|
||||
DefaultLaunchScript script = new DefaultLaunchScript(null, null);
|
||||
String content = new String(script.toByteArray());
|
||||
assertThat(content, containsString("Spring Boot Startup Script"));
|
||||
assertThat(content, containsString("mode=\"auto\""));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loadFromFile() throws Exception {
|
||||
File file = this.temporaryFolder.newFile();
|
||||
FileCopyUtils.copy("ABC".getBytes(), file);
|
||||
DefaultLaunchScript script = new DefaultLaunchScript(file, null);
|
||||
String content = new String(script.toByteArray());
|
||||
assertThat(content, equalTo("ABC"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void expandVariables() throws Exception {
|
||||
File file = this.temporaryFolder.newFile();
|
||||
FileCopyUtils.copy("h{{a}}ll{{b}}".getBytes(), file);
|
||||
Properties properties = new Properties();
|
||||
properties.put("a", "e");
|
||||
properties.put("b", "o");
|
||||
DefaultLaunchScript script = new DefaultLaunchScript(file, properties);
|
||||
String content = new String(script.toByteArray());
|
||||
assertThat(content, equalTo("hello"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void expandVariablesMultiLine() throws Exception {
|
||||
File file = this.temporaryFolder.newFile();
|
||||
FileCopyUtils.copy("h{{a}}l\nl{{b}}".getBytes(), file);
|
||||
Properties properties = new Properties();
|
||||
properties.put("a", "e");
|
||||
properties.put("b", "o");
|
||||
DefaultLaunchScript script = new DefaultLaunchScript(file, properties);
|
||||
String content = new String(script.toByteArray());
|
||||
assertThat(content, equalTo("hel\nlo"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void expandVariablesWithDefaults() throws Exception {
|
||||
File file = this.temporaryFolder.newFile();
|
||||
FileCopyUtils.copy("h{{a:e}}ll{{b:o}}".getBytes(), file);
|
||||
DefaultLaunchScript script = new DefaultLaunchScript(file, null);
|
||||
String content = new String(script.toByteArray());
|
||||
assertThat(content, equalTo("hello"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void expandVariablesWithDefaultsOverride() throws Exception {
|
||||
File file = this.temporaryFolder.newFile();
|
||||
FileCopyUtils.copy("h{{a:e}}ll{{b:o}}".getBytes(), file);
|
||||
Properties properties = new Properties();
|
||||
properties.put("a", "a");
|
||||
DefaultLaunchScript script = new DefaultLaunchScript(file, properties);
|
||||
String content = new String(script.toByteArray());
|
||||
assertThat(content, equalTo("hallo"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void expandVariablesMissingAreUnchanged() throws Exception {
|
||||
File file = this.temporaryFolder.newFile();
|
||||
FileCopyUtils.copy("h{{a}}ll{{b}}".getBytes(), file);
|
||||
DefaultLaunchScript script = new DefaultLaunchScript(file, null);
|
||||
String content = new String(script.toByteArray());
|
||||
assertThat(content, equalTo("h{{a}}ll{{b}}"));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -18,6 +18,8 @@ package org.springframework.boot.loader.tools;
|
|||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.attribute.PosixFilePermission;
|
||||
import java.util.jar.Attributes;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarFile;
|
||||
|
|
@ -34,6 +36,7 @@ import org.springframework.boot.loader.tools.sample.ClassWithoutMainMethod;
|
|||
import org.springframework.util.FileCopyUtils;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.hasItem;
|
||||
import static org.hamcrest.Matchers.startsWith;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
|
|
@ -141,7 +144,6 @@ public class RepackagerTests {
|
|||
Repackager repackager = new Repackager(file);
|
||||
repackager.repackage(NO_LIBRARIES);
|
||||
repackager.repackage(NO_LIBRARIES);
|
||||
|
||||
Manifest actualManifest = getManifest(file);
|
||||
assertThat(actualManifest.getMainAttributes().getValue("Main-Class"),
|
||||
equalTo("org.springframework.boot.loader.JarLauncher"));
|
||||
|
|
@ -230,7 +232,6 @@ public class RepackagerTests {
|
|||
equalTo(false));
|
||||
assertThat(hasLauncherClasses(source), equalTo(false));
|
||||
assertThat(hasLauncherClasses(dest), equalTo(true));
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -380,7 +381,6 @@ public class RepackagerTests {
|
|||
callback.library(new Library(nestedFile, LibraryScope.COMPILE));
|
||||
}
|
||||
});
|
||||
|
||||
JarFile jarFile = new JarFile(file);
|
||||
try {
|
||||
assertThat(jarFile.getEntry("lib/" + nestedFile.getName()).getMethod(),
|
||||
|
|
@ -393,6 +393,22 @@ public class RepackagerTests {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addLauncherScript() throws Exception {
|
||||
this.testJarFile.addClass("a/b/C.class", ClassWithMainMethod.class);
|
||||
File source = this.testJarFile.getFile();
|
||||
File dest = this.temporaryFolder.newFile("dest.jar");
|
||||
Repackager repackager = new Repackager(source);
|
||||
LaunchScript script = new MockLauncherScript("ABC");
|
||||
repackager.repackage(dest, NO_LIBRARIES, script);
|
||||
byte[] bytes = FileCopyUtils.copyToByteArray(dest);
|
||||
assertThat(Files.getPosixFilePermissions(dest.toPath()),
|
||||
hasItem(PosixFilePermission.OWNER_EXECUTE));
|
||||
assertThat(new String(bytes), startsWith("ABC"));
|
||||
assertThat(hasLauncherClasses(source), equalTo(false));
|
||||
assertThat(hasLauncherClasses(dest), equalTo(true));
|
||||
}
|
||||
|
||||
private boolean hasLauncherClasses(File file) throws IOException {
|
||||
return hasEntry(file, "org/springframework/boot/")
|
||||
&& hasEntry(file, "org/springframework/boot/loader/JarLauncher.class");
|
||||
|
|
@ -422,4 +438,19 @@ public class RepackagerTests {
|
|||
}
|
||||
}
|
||||
|
||||
private static class MockLauncherScript implements LaunchScript {
|
||||
|
||||
private final byte[] bytes;
|
||||
|
||||
public MockLauncherScript(String script) {
|
||||
this.bytes = script.getBytes();
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] toByteArray() {
|
||||
return this.bytes;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,61 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>org.springframework.boot.maven.it</groupId>
|
||||
<artifactId>jar</artifactId>
|
||||
<version>0.0.1.BUILD-SNAPSHOT</version>
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>@project.groupId@</groupId>
|
||||
<artifactId>@project.artifactId@</artifactId>
|
||||
<version>@project.version@</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>repackage</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<embeddedLaunchScript>${basedir}/src/launcher/custom.script</embeddedLaunchScript>
|
||||
<embeddedLaunchScriptProperties>
|
||||
<name>world</name>
|
||||
</embeddedLaunchScriptProperties>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<version>2.4</version>
|
||||
<configuration>
|
||||
<archive>
|
||||
<manifest>
|
||||
<mainClass>some.random.Main</mainClass>
|
||||
</manifest>
|
||||
<manifestEntries>
|
||||
<Not-Used>Foo</Not-Used>
|
||||
</manifestEntries>
|
||||
</archive>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-context</artifactId>
|
||||
<version>@spring.version@</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.servlet</groupId>
|
||||
<artifactId>javax.servlet-api</artifactId>
|
||||
<version>@servlet-api.version@</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
#!/bin/sh
|
||||
|
||||
echo "Hello {{name}}"
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* Copyright 2012-2014 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.test;
|
||||
|
||||
public class SampleApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
import java.io.*;
|
||||
import org.springframework.boot.maven.*;
|
||||
|
||||
Verify.verifyJar(
|
||||
new File( basedir, "target/jar-0.0.1.BUILD-SNAPSHOT.jar" ), "some.random.Main", "Hello world"
|
||||
);
|
||||
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>org.springframework.boot.maven.it</groupId>
|
||||
<artifactId>jar</artifactId>
|
||||
<version>0.0.1.BUILD-SNAPSHOT</version>
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>@project.groupId@</groupId>
|
||||
<artifactId>@project.artifactId@</artifactId>
|
||||
<version>@project.version@</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>repackage</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<executable>false</executable>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<version>2.4</version>
|
||||
<configuration>
|
||||
<archive>
|
||||
<manifest>
|
||||
<mainClass>some.random.Main</mainClass>
|
||||
</manifest>
|
||||
<manifestEntries>
|
||||
<Not-Used>Foo</Not-Used>
|
||||
</manifestEntries>
|
||||
</archive>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-context</artifactId>
|
||||
<version>@spring.version@</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.servlet</groupId>
|
||||
<artifactId>javax.servlet-api</artifactId>
|
||||
<version>@servlet-api.version@</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* Copyright 2012-2014 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.test;
|
||||
|
||||
public class SampleApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
import java.io.*;
|
||||
import org.springframework.boot.maven.*;
|
||||
|
||||
Verify.verifyJar(
|
||||
new File( basedir, "target/jar-0.0.1.BUILD-SNAPSHOT.jar" ), "some.random.Main", false
|
||||
);
|
||||
|
||||
|
|
@ -2,6 +2,6 @@ import java.io.*;
|
|||
import org.springframework.boot.maven.*;
|
||||
|
||||
Verify.verifyJar(
|
||||
new File( basedir, "target/jar-0.0.1.BUILD-SNAPSHOT.jar" ), "some.random.Main"
|
||||
new File( basedir, "target/jar-0.0.1.BUILD-SNAPSHOT.jar" ), "some.random.Main", "Spring Boot Startup Script"
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ package org.springframework.boot.maven;
|
|||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.jar.JarFile;
|
||||
|
|
@ -34,6 +35,8 @@ import org.apache.maven.plugins.annotations.Parameter;
|
|||
import org.apache.maven.plugins.annotations.ResolutionScope;
|
||||
import org.apache.maven.project.MavenProject;
|
||||
import org.apache.maven.project.MavenProjectHelper;
|
||||
import org.springframework.boot.loader.tools.DefaultLaunchScript;
|
||||
import org.springframework.boot.loader.tools.LaunchScript;
|
||||
import org.springframework.boot.loader.tools.Layout;
|
||||
import org.springframework.boot.loader.tools.Layouts;
|
||||
import org.springframework.boot.loader.tools.Libraries;
|
||||
|
|
@ -124,6 +127,29 @@ public class RepackageMojo extends AbstractDependencyFilterMojo {
|
|||
@Parameter
|
||||
private List<Dependency> requiresUnpack;
|
||||
|
||||
/**
|
||||
* Make a fully executable jar for *nix machines by prepending a launch script to the
|
||||
* jar.
|
||||
* @since 1.3
|
||||
*/
|
||||
@Parameter(defaultValue = "true")
|
||||
private boolean executable;
|
||||
|
||||
/**
|
||||
* The embedded launch script to prepend to the front of the jar if it is fully
|
||||
* executable. If not specified the 'Spring Boot' default script will be used.
|
||||
* @since 1.3
|
||||
*/
|
||||
@Parameter
|
||||
private File embeddedLaunchScript;
|
||||
|
||||
/**
|
||||
* Properties that should be expanded in the embedded launch script.
|
||||
* @since 1.3
|
||||
*/
|
||||
@Parameter
|
||||
private Properties embeddedLaunchScriptProperties;
|
||||
|
||||
@Override
|
||||
public void execute() throws MojoExecutionException, MojoFailureException {
|
||||
if (this.project.getPackaging().equals("pom")) {
|
||||
|
|
@ -167,7 +193,8 @@ public class RepackageMojo extends AbstractDependencyFilterMojo {
|
|||
Libraries libraries = new ArtifactsLibraries(artifacts, this.requiresUnpack,
|
||||
getLog());
|
||||
try {
|
||||
repackager.repackage(target, libraries);
|
||||
LaunchScript launchScript = getLaunchScript();
|
||||
repackager.repackage(target, libraries, launchScript);
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new MojoExecutionException(ex.getMessage(), ex);
|
||||
|
|
@ -190,6 +217,14 @@ public class RepackageMojo extends AbstractDependencyFilterMojo {
|
|||
+ this.project.getPackaging());
|
||||
}
|
||||
|
||||
private LaunchScript getLaunchScript() throws IOException {
|
||||
if (this.executable) {
|
||||
return new DefaultLaunchScript(this.embeddedLaunchScript,
|
||||
this.embeddedLaunchScriptProperties);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static enum LayoutType {
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -26,8 +26,12 @@ import java.util.jar.Manifest;
|
|||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipFile;
|
||||
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
/**
|
||||
|
|
@ -44,8 +48,14 @@ public class Verify {
|
|||
new JarArchiveVerification(file, SAMPLE_APP).verify();
|
||||
}
|
||||
|
||||
public static void verifyJar(File file, String main) throws Exception {
|
||||
new JarArchiveVerification(file, main).verify();
|
||||
public static void verifyJar(File file, String main, String... scriptContents)
|
||||
throws Exception {
|
||||
verifyJar(file, main, true, scriptContents);
|
||||
}
|
||||
|
||||
public static void verifyJar(File file, String main, boolean executable,
|
||||
String... scriptContents) throws Exception {
|
||||
new JarArchiveVerification(file, main).verify(executable, scriptContents);
|
||||
}
|
||||
|
||||
public static void verifyWar(File file) throws Exception {
|
||||
|
|
@ -149,9 +159,30 @@ public class Verify {
|
|||
}
|
||||
|
||||
public void verify() throws Exception {
|
||||
verify(true);
|
||||
}
|
||||
|
||||
public void verify(boolean executable, String... scriptContents) throws Exception {
|
||||
assertTrue("Archive missing", this.file.exists());
|
||||
assertTrue("Archive not a file", this.file.isFile());
|
||||
|
||||
if (scriptContents.length > 0 && executable) {
|
||||
String contents = new String(FileCopyUtils.copyToByteArray(this.file));
|
||||
contents = contents.substring(0, contents.indexOf(new String(new byte[] {
|
||||
0x50, 0x4b, 0x03, 0x04 })));
|
||||
for (String content : scriptContents) {
|
||||
assertThat(contents, containsString(content));
|
||||
}
|
||||
}
|
||||
|
||||
if (!executable) {
|
||||
String contents = new String(FileCopyUtils.copyToByteArray(this.file));
|
||||
assertTrue(
|
||||
"Is executable",
|
||||
contents.startsWith(new String(new byte[] { 0x50, 0x4b, 0x03,
|
||||
0x04 })));
|
||||
}
|
||||
|
||||
ZipFile zipFile = new ZipFile(this.file);
|
||||
try {
|
||||
ArchiveVerifier verifier = new ArchiveVerifier(zipFile);
|
||||
|
|
|
|||
Loading…
Reference in New Issue