Commit 47cd5dd6 authored by Dave Syer's avatar Dave Syer

Tooling for PropertiesLauncher in JAR archives

To use PropertiesLauncher instead of JarLauncher in an
executable JAR we have provided tooling support. In Maven
(using the starter parent to default some of the settings):

    <plugin>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-maven-plugin</artifactId>
      <configuration>
        <layout>ZIP</layout>
      </configuration>
    </plugin>

in Gradle:

    apply plugin: "spring-boot"
    springBoot {
        layout = 'ZIP'
      }
    }

[Fixes #58837492] [bs-330] Add tooling for PropertiesLauncher
parent 837070d6
...@@ -16,13 +16,35 @@ ...@@ -16,13 +16,35 @@
package org.springframework.boot.gradle package org.springframework.boot.gradle
import org.springframework.boot.loader.tools.Layout
import org.springframework.boot.loader.tools.Layouts
/** /**
* Gradle DSL Extension for 'Spring Boot'. * Gradle DSL Extension for 'Spring Boot'. Most of the time Spring Boot can guess the
* settings in this extension, but occasionally you might need to explicitly set one
* or two of them. E.g.
*
* <pre>
* apply plugin: "spring-boot"
* springBoot {
* mainClass = 'org.demo.Application'
* layout = 'ZIP'
* }
* </pre>
* *
* @author Phillip Webb * @author Phillip Webb
* @author Dave Syer
*/ */
public class SpringBootPluginExtension { public class SpringBootPluginExtension {
static enum LayoutType {
JAR(new Layouts.Jar()), WAR(new Layouts.War()), ZIP(new Layouts.Expanded()), DIR(new Layouts.Expanded());
Layout layout;
private LayoutType(Layout layout) {
this.layout = layout;
}
}
/** /**
* The main class that should be run. If not specified the value from the * The main class that should be run. If not specified the value from the
* MANIFEST will be used, or if no manifest entry is the archive will be * MANIFEST will be used, or if no manifest entry is the archive will be
...@@ -31,7 +53,8 @@ public class SpringBootPluginExtension { ...@@ -31,7 +53,8 @@ public class SpringBootPluginExtension {
String mainClass String mainClass
/** /**
* The name of the provided configuration. If not specified 'providedRuntime' will * The name of the ivy configuration name to treat as 'provided' (when packaging
* those dependencies in a separate path). If not specified 'providedRuntime' will
* be used. * be used.
*/ */
String providedConfiguration String providedConfiguration
...@@ -40,4 +63,23 @@ public class SpringBootPluginExtension { ...@@ -40,4 +63,23 @@ public class SpringBootPluginExtension {
* If the original source archive should be backed-up before being repackaged. * If the original source archive should be backed-up before being repackaged.
*/ */
boolean backupSource = true; boolean backupSource = true;
/**
* The layout of the archive if it can't be derived from the file extension.
* Valid values are JAR, WAR, ZIP, DIR (for exploded zip file). ZIP and DIR
* are actually synonymous, and should be used if there is no MANIFEST.MF
* available, or if you want the MANIFEST.MF 'Main-Class' to be
* PropertiesLauncher. Gradle will coerce literal String values to the
* correct type.
*/
LayoutType layout;
/**
* Convenience method for use in a custom task.
*
* @return the Layout to use or null if not explicitly set
*/
Layout convertLayout() {
layout==null ? null : layout.layout
}
} }
...@@ -50,11 +50,13 @@ public class Repackage extends DefaultTask { ...@@ -50,11 +50,13 @@ public class Repackage extends DefaultTask {
if (file.exists()) { if (file.exists()) {
Repackager repackager = new Repackager(file); Repackager repackager = new Repackager(file);
repackager.setMainClass(extension.getMainClass()); repackager.setMainClass(extension.getMainClass());
if (extension.convertLayout() != null) {
repackager.setLayout(extension.convertLayout());
}
repackager.setBackupSource(extension.isBackupSource()); repackager.setBackupSource(extension.isBackupSource());
try { try {
repackager.repackage(libraries); repackager.repackage(libraries);
} } catch (IOException ex) {
catch (IOException ex) {
throw new IllegalStateException(ex.getMessage(), ex); throw new IllegalStateException(ex.getMessage(), ex);
} }
} }
......
#Generated by Git-Commit-Id-Plugin #Generated by Git-Commit-Id-Plugin
#Tue Oct 15 11:07:38 EDT 2013 #Tue Oct 15 11:35:18 EDT 2013
git.commit.id.abbrev=d3fa609 git.commit.id.abbrev=ea11daf
git.commit.user.email=dsyer@gopivotal.com git.commit.user.email=dsyer@gopivotal.com
git.commit.message.full=Extend PropertiesLauncher to load nested archives\n\nPropertiesLauncher can now be used to run an executable jar, and by\ndefault it will pick up nested archives in lib/ (where the Boot\ntools puts them). User can provide loader.path (colon-separated)\nto change the nested path.\n\n[\#58837492] [bs-330] Add tooling for PropertiesLauncher\n git.commit.message.full=Extend PropertiesLauncher to load nested archives\n\nPropertiesLauncher can now be used to run an executable jar, and by\ndefault it will pick up nested archives in lib/ (where the Boot\ntools puts them). User can provide loader.path (colon-separated)\nto change the nested path.\n\n[\#58837492] [bs-330] Add tooling for PropertiesLauncher\n
git.commit.id=d3fa60955b06fe78bbf0c914928d794661aca312 git.commit.id=ea11dafcbd951b3b23257585bce1f479ca9faa73
git.commit.message.short=Extend PropertiesLauncher to load nested archives git.commit.message.short=Extend PropertiesLauncher to load nested archives
git.commit.user.name=Dave Syer git.commit.user.name=Dave Syer
git.build.user.name=Dave Syer git.build.user.name=Dave Syer
git.build.user.email=dsyer@gopivotal.com git.build.user.email=dsyer@gopivotal.com
git.branch=master git.branch=master
git.commit.time=2013-10-15T10\:51\:03-0400 git.commit.time=2013-10-15T11\:08\:45-0400
git.build.time=2013-10-15T11\:07\:38-0400 git.build.time=2013-10-15T11\:35\:18-0400
...@@ -43,6 +43,9 @@ public class Layouts { ...@@ -43,6 +43,9 @@ public class Layouts {
if (file.getName().toLowerCase().endsWith(".war")) { if (file.getName().toLowerCase().endsWith(".war")) {
return new War(); return new War();
} }
if (file.isDirectory() || file.getName().toLowerCase().endsWith(".zip")) {
return new Expanded();
}
throw new IllegalStateException("Unable to deduce layout for '" + file + "'"); throw new IllegalStateException("Unable to deduce layout for '" + file + "'");
} }
...@@ -67,6 +70,18 @@ public class Layouts { ...@@ -67,6 +70,18 @@ public class Layouts {
} }
} }
/**
* Executable expanded archive layout.
*/
public static class Expanded extends Jar {
@Override
public String getLauncherClassName() {
return "org.springframework.boot.loader.PropertiesLauncher";
}
}
/** /**
* Executable WAR layout. * Executable WAR layout.
*/ */
......
#Generated by Git-Commit-Id-Plugin #Generated by Git-Commit-Id-Plugin
#Tue Oct 15 11:07:33 EDT 2013 #Wed Oct 16 08:14:33 EDT 2013
git.commit.id.abbrev=d3fa609 git.commit.id.abbrev=a7ba9ba
git.commit.user.email=dsyer@gopivotal.com git.commit.user.email=dsyer@gopivotal.com
git.commit.message.full=Extend PropertiesLauncher to load nested archives\n\nPropertiesLauncher can now be used to run an executable jar, and by\ndefault it will pick up nested archives in lib/ (where the Boot\ntools puts them). User can provide loader.path (colon-separated)\nto change the nested path.\n\n[\#58837492] [bs-330] Add tooling for PropertiesLauncher\n git.commit.message.full=Tooling for PropertiesLauncher\n
git.commit.id=d3fa60955b06fe78bbf0c914928d794661aca312 git.commit.id=a7ba9ba5cd47a924f9c7668a772957fc05ffa058
git.commit.message.short=Extend PropertiesLauncher to load nested archives git.commit.message.short=Tooling for PropertiesLauncher
git.commit.user.name=Dave Syer git.commit.user.name=Dave Syer
git.build.user.name=Dave Syer git.build.user.name=Dave Syer
git.build.user.email=dsyer@gopivotal.com git.build.user.email=dsyer@gopivotal.com
git.branch=master git.branch=feature/proptool
git.commit.time=2013-10-15T10\:51\:03-0400 git.commit.time=2013-10-15T16\:54\:14-0400
git.build.time=2013-10-15T11\:07\:33-0400 git.build.time=2013-10-16T08\:14\:33-0400
...@@ -47,9 +47,21 @@ public abstract class Archive { ...@@ -47,9 +47,21 @@ public abstract class Archive {
* @throws Exception * @throws Exception
*/ */
public String getMainClass() throws Exception { public String getMainClass() throws Exception {
String mainClass = getManifest().getMainAttributes().getValue("Start-Class"); Manifest manifest = getManifest();
String mainClass = null;
if (manifest != null) {
mainClass = manifest.getMainAttributes().getValue("Start-Class");
}
if (mainClass == null) { if (mainClass == null) {
throw new IllegalStateException("No 'Start-Class' manifest entry specified"); String url = "UNKNOWN";
try {
url = getUrl().toString();
}
catch (Exception e) {
// ignore
}
throw new IllegalStateException(
"No 'Start-Class' manifest entry specified in " + url);
} }
return mainClass; return mainClass;
} }
......
#Generated by Git-Commit-Id-Plugin #Generated by Git-Commit-Id-Plugin
#Tue Oct 15 11:07:32 EDT 2013 #Wed Oct 16 08:17:37 EDT 2013
git.commit.id.abbrev=d3fa609 git.commit.id.abbrev=a7ba9ba
git.commit.user.email=dsyer@gopivotal.com git.commit.user.email=dsyer@gopivotal.com
git.commit.message.full=Extend PropertiesLauncher to load nested archives\n\nPropertiesLauncher can now be used to run an executable jar, and by\ndefault it will pick up nested archives in lib/ (where the Boot\ntools puts them). User can provide loader.path (colon-separated)\nto change the nested path.\n\n[\#58837492] [bs-330] Add tooling for PropertiesLauncher\n git.commit.message.full=Tooling for PropertiesLauncher\n
git.commit.id=d3fa60955b06fe78bbf0c914928d794661aca312 git.commit.id=a7ba9ba5cd47a924f9c7668a772957fc05ffa058
git.commit.message.short=Extend PropertiesLauncher to load nested archives git.commit.message.short=Tooling for PropertiesLauncher
git.commit.user.name=Dave Syer git.commit.user.name=Dave Syer
git.build.user.name=Dave Syer git.build.user.name=Dave Syer
git.build.user.email=dsyer@gopivotal.com git.build.user.email=dsyer@gopivotal.com
git.branch=master git.branch=feature/proptool
git.commit.time=2013-10-15T10\:51\:03-0400 git.commit.time=2013-10-15T16\:54\:14-0400
git.build.time=2013-10-15T11\:07\:32-0400 git.build.time=2013-10-16T08\:17\:37-0400
<?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>
<packaging>jar</packaging>
<build>
<plugins>
<plugin>
<groupId>@project.groupId@</groupId>
<artifactId>@project.artifactId@</artifactId>
<version>@project.version@</version>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
<configuration>
<layout>ZIP</layout>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.4</version>
<configuration>
<archive>
<manifestEntries>
<Not-Used>Foo</Not-Used>
</manifestEntries>
</archive>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>3.2.3.RELEASE</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.0.1</version>
<scope>provided</scope>
</dependency>
</dependencies>
</project>
package org.test;
public class SampleApplication {
public static void main(String[] args) {
}
}
import java.io.*;
import org.springframework.boot.maven.*;
Verify.verifyZip(
new File( basedir, "target/jar-0.0.1.BUILD-SNAPSHOT.jar" )
);
...@@ -29,6 +29,8 @@ import org.apache.maven.plugins.annotations.Parameter; ...@@ -29,6 +29,8 @@ import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.plugins.annotations.ResolutionScope; import org.apache.maven.plugins.annotations.ResolutionScope;
import org.apache.maven.project.MavenProject; import org.apache.maven.project.MavenProject;
import org.apache.maven.project.MavenProjectHelper; import org.apache.maven.project.MavenProjectHelper;
import org.springframework.boot.loader.tools.Layout;
import org.springframework.boot.loader.tools.Layouts;
import org.springframework.boot.loader.tools.Libraries; import org.springframework.boot.loader.tools.Libraries;
import org.springframework.boot.loader.tools.Repackager; import org.springframework.boot.loader.tools.Repackager;
...@@ -81,12 +83,22 @@ public class RepackageMojo extends AbstractMojo { ...@@ -81,12 +83,22 @@ public class RepackageMojo extends AbstractMojo {
@Parameter @Parameter
private String mainClass; private String mainClass;
/**
* The layout to use (JAR, WAR, ZIP, DIR) in case it cannot be inferred.
*/
@Parameter
private LayoutType layout;
@Override @Override
public void execute() throws MojoExecutionException, MojoFailureException { public void execute() throws MojoExecutionException, MojoFailureException {
File source = this.project.getArtifact().getFile(); File source = this.project.getArtifact().getFile();
File target = getTargetFile(); File target = getTargetFile();
Repackager repackager = new Repackager(source); Repackager repackager = new Repackager(source);
repackager.setMainClass(this.mainClass); repackager.setMainClass(this.mainClass);
if (this.layout != null) {
getLog().info("Layout: " + this.layout);
repackager.setLayout(this.layout.layout());
}
Libraries libraries = new ArtifactsLibraries(this.project.getArtifacts()); Libraries libraries = new ArtifactsLibraries(this.project.getArtifacts());
try { try {
repackager.repackage(target, libraries); repackager.repackage(target, libraries);
...@@ -112,4 +124,18 @@ public class RepackageMojo extends AbstractMojo { ...@@ -112,4 +124,18 @@ public class RepackageMojo extends AbstractMojo {
+ this.project.getPackaging()); + this.project.getPackaging());
} }
public static enum LayoutType {
JAR(new Layouts.Jar()), WAR(new Layouts.War()), ZIP(new Layouts.Expanded()), DIR(
new Layouts.Expanded());
private Layout layout;
public Layout layout() {
return this.layout;
}
private LayoutType(Layout layout) {
this.layout = layout;
}
}
} }
#Generated by Git-Commit-Id-Plugin #Generated by Git-Commit-Id-Plugin
#Tue Oct 15 11:07:34 EDT 2013 #Wed Oct 16 08:42:05 EDT 2013
git.commit.id.abbrev=d3fa609 git.commit.id.abbrev=a7ba9ba
git.commit.user.email=dsyer@gopivotal.com git.commit.user.email=dsyer@gopivotal.com
git.commit.message.full=Extend PropertiesLauncher to load nested archives\n\nPropertiesLauncher can now be used to run an executable jar, and by\ndefault it will pick up nested archives in lib/ (where the Boot\ntools puts them). User can provide loader.path (colon-separated)\nto change the nested path.\n\n[\#58837492] [bs-330] Add tooling for PropertiesLauncher\n git.commit.message.full=Tooling for PropertiesLauncher\n
git.commit.id=d3fa60955b06fe78bbf0c914928d794661aca312 git.commit.id=a7ba9ba5cd47a924f9c7668a772957fc05ffa058
git.commit.message.short=Extend PropertiesLauncher to load nested archives git.commit.message.short=Tooling for PropertiesLauncher
git.commit.user.name=Dave Syer git.commit.user.name=Dave Syer
git.build.user.name=Dave Syer git.build.user.name=Dave Syer
git.build.user.email=dsyer@gopivotal.com git.build.user.email=dsyer@gopivotal.com
git.branch=master git.branch=feature/proptool
git.commit.time=2013-10-15T10\:51\:03-0400 git.commit.time=2013-10-15T16\:54\:14-0400
git.build.time=2013-10-15T11\:07\:34-0400 git.build.time=2013-10-16T08\:42\:05-0400
...@@ -46,6 +46,10 @@ public class Verify { ...@@ -46,6 +46,10 @@ public class Verify {
new WarArchiveVerification(file).verify(); new WarArchiveVerification(file).verify();
} }
public static void verifyZip(File file) throws Exception {
new ZipArchiveVerification(file).verify();
}
private static abstract class AbstractArchiveVerification { private static abstract class AbstractArchiveVerification {
private File file; private File file;
...@@ -154,4 +158,20 @@ public class Verify { ...@@ -154,4 +158,20 @@ public class Verify {
} }
} }
private static class ZipArchiveVerification extends AbstractArchiveVerification {
public ZipArchiveVerification(File file) {
super(file);
}
@Override
protected void verifyManifest(Manifest manifest) throws Exception {
assertEquals("org.springframework.boot.loader.PropertiesLauncher", manifest
.getMainAttributes().getValue("Main-Class"));
assertEquals("org.test.SampleApplication", manifest.getMainAttributes()
.getValue("Start-Class"));
assertEquals("Foo", manifest.getMainAttributes().getValue("Not-Used"));
}
}
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment