Support embedded jar initialization scripts

Update the Maven and Gradle plugin to generate fully executable jar
files on Unix like machines. A launcher bash script is added to the
front of the jar file which handles execution.

The default execution script will either launch the application or
handle init.d service operations (start/stop/restart) depending on if
the application is executed directly, or via a symlink to init.d.

See gh-1117
This commit is contained in:
Phillip Webb
2015-04-09 10:51:21 -07:00
parent ffc5d565c8
commit 793481843c
19 changed files with 796 additions and 18 deletions

View File

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

View File

@@ -0,0 +1,3 @@
#!/bin/sh
echo "Hello {{name}}"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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 {
/**

View File

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