Commit 2c691e5a authored by Dave Syer's avatar Dave Syer

Enhance JarCommand to support lists of includes and excludes

The lists are comma separated. In addition, user can add prefixes
"+" or "-", to signal that those values should be removed from the
default list, not added to a fresh one. E.g.

$ spring jar app.jar --include lib/*.jar,-static/** --exclude -**/*.jar

to include a jar file specifically, and make sure it is not excluded,
and additionally not include the static/** resources that would otherwise
be included in the defaults. As soon as "+" or "-" prefixes are detected
the default entries are all added (except the ones exlcuded with "-").

Fixes gh-1090
parent d8424461
...@@ -26,6 +26,7 @@ import org.springframework.boot.loader.tools.JavaExecutable; ...@@ -26,6 +26,7 @@ import org.springframework.boot.loader.tools.JavaExecutable;
import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.not;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat; import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
...@@ -80,7 +81,32 @@ public class JarCommandIT { ...@@ -80,7 +81,32 @@ public class JarCommandIT {
assertThat(invocation.getStandardOutput(), containsString("/static/static.txt")); assertThat(invocation.getStandardOutput(), containsString("/static/static.txt"));
assertThat(invocation.getStandardOutput(), assertThat(invocation.getStandardOutput(),
containsString("/templates/template.txt")); containsString("/templates/template.txt"));
assertThat(invocation.getStandardOutput(), containsString("Goodbye Mama"));
}
@Test
public void jarCreationWithIncludes() throws Exception {
File jar = new File("target/test-app.jar");
Invocation invocation = this.cli.invoke("jar", jar.getAbsolutePath(),
"--include", "-public/**,-resources/**", "jar.groovy");
invocation.await();
assertEquals(invocation.getErrorOutput(), 0, invocation.getErrorOutput().length());
assertTrue(jar.exists());
Process process = new JavaExecutable().processBuilder("-jar",
jar.getAbsolutePath()).start();
invocation = new Invocation(process);
invocation.await();
assertThat(invocation.getErrorOutput(), equalTo(""));
assertThat(invocation.getStandardOutput(), containsString("Hello World!"));
assertThat(invocation.getStandardOutput(),
not(containsString("/public/public.txt")));
assertThat(invocation.getStandardOutput(), assertThat(invocation.getStandardOutput(),
containsString("Goodbye Mama")); not(containsString("/resources/resource.txt")));
assertThat(invocation.getStandardOutput(), containsString("/static/static.txt"));
assertThat(invocation.getStandardOutput(),
containsString("/templates/template.txt"));
assertThat(invocation.getStandardOutput(), containsString("Goodbye Mama"));
} }
} }
...@@ -169,7 +169,9 @@ public class CommandRunner implements Iterable<Command> { ...@@ -169,7 +169,9 @@ public class CommandRunner implements Iterable<Command> {
try { try {
ExitStatus result = run(argsWithoutDebugFlags); ExitStatus result = run(argsWithoutDebugFlags);
// The caller will hang up if it gets a non-zero status // The caller will hang up if it gets a non-zero status
return result==null ? 0 : result.isHangup() ? (result.getCode()>0 ? result.getCode() : 1) : 0; return result == null ? 0
: result.isHangup() ? (result.getCode() > 0 ? result.getCode() : 0)
: 0;
} }
catch (NoArgumentsException ex) { catch (NoArgumentsException ex) {
showUsage(); showUsage();
......
...@@ -68,12 +68,6 @@ import org.springframework.util.Assert; ...@@ -68,12 +68,6 @@ import org.springframework.util.Assert;
*/ */
public class JarCommand extends OptionParsingCommand { public class JarCommand extends OptionParsingCommand {
private static final String[] DEFAULT_INCLUDES = { "public/**", "resources/**",
"static/**", "templates/**", "META-INF/**", "*" };
private static final String[] DEFAULT_EXCLUDES = { ".*", "repository/**", "build/**",
"target/**", "**/*.jar", "**/*.groovy" };
private static final Layout LAYOUT = new Layouts.Jar(); private static final Layout LAYOUT = new Layouts.Jar();
public JarCommand() { public JarCommand() {
...@@ -98,11 +92,11 @@ public class JarCommand extends OptionParsingCommand { ...@@ -98,11 +92,11 @@ public class JarCommand extends OptionParsingCommand {
this.includeOption = option( this.includeOption = option(
"include", "include",
"Pattern applied to directories on the classpath to find files to include in the resulting jar") "Pattern applied to directories on the classpath to find files to include in the resulting jar")
.withRequiredArg().defaultsTo(DEFAULT_INCLUDES); .withRequiredArg().withValuesSeparatedBy(",").defaultsTo("");
this.excludeOption = option( this.excludeOption = option(
"exclude", "exclude",
"Pattern applied to directories on the claspath to find files to exclude from the resulting jar") "Pattern applied to directories on the claspath to find files to exclude from the resulting jar")
.withRequiredArg().defaultsTo(DEFAULT_EXCLUDES); .withRequiredArg().withValuesSeparatedBy(",").defaultsTo("");
} }
@Override @Override
......
...@@ -23,7 +23,9 @@ import java.net.URL; ...@@ -23,7 +23,9 @@ import java.net.URL;
import java.net.URLClassLoader; import java.net.URLClassLoader;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Enumeration; import java.util.Enumeration;
import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.Set;
import org.springframework.core.io.DefaultResourceLoader; import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.FileSystemResource; import org.springframework.core.io.FileSystemResource;
...@@ -39,6 +41,12 @@ import org.springframework.util.AntPathMatcher; ...@@ -39,6 +41,12 @@ import org.springframework.util.AntPathMatcher;
*/ */
class ResourceMatcher { class ResourceMatcher {
private static final String[] DEFAULT_INCLUDES = { "public/**", "resources/**",
"static/**", "templates/**", "META-INF/**", "*" };
private static final String[] DEFAULT_EXCLUDES = { ".*", "repository/**", "build/**",
"target/**", "**/*.jar", "**/*.groovy" };
private final AntPathMatcher pathMatcher = new AntPathMatcher(); private final AntPathMatcher pathMatcher = new AntPathMatcher();
private final List<String> includes; private final List<String> includes;
...@@ -46,8 +54,8 @@ class ResourceMatcher { ...@@ -46,8 +54,8 @@ class ResourceMatcher {
private final List<String> excludes; private final List<String> excludes;
ResourceMatcher(List<String> includes, List<String> excludes) { ResourceMatcher(List<String> includes, List<String> excludes) {
this.includes = includes; this.includes = getOptions(includes, DEFAULT_INCLUDES);
this.excludes = excludes; this.excludes = getOptions(excludes, DEFAULT_EXCLUDES);
} }
public List<MatchedResource> find(List<File> roots) throws IOException { public List<MatchedResource> find(List<File> roots) throws IOException {
...@@ -93,6 +101,33 @@ class ResourceMatcher { ...@@ -93,6 +101,33 @@ class ResourceMatcher {
return false; return false;
} }
private List<String> getOptions(List<String> values, String[] defaults) {
Set<String> result = new LinkedHashSet<String>();
Set<String> minus = new LinkedHashSet<String>();
boolean deltasFound = false;
for (String value : values) {
if (value.startsWith("+")) {
deltasFound = true;
value = value.substring(1);
result.add(value);
}
else if (value.startsWith("-")) {
deltasFound = true;
value = value.substring(1);
minus.add(value);
}
else if (value.trim().length() > 0) {
result.add(value);
}
}
for (String value : defaults) {
if (!minus.contains(value) || !deltasFound) {
result.add(value);
}
}
return new ArrayList<String>(result);
}
/** /**
* {@link ResourceLoader} to get load resource from a folder. * {@link ResourceLoader} to get load resource from a folder.
*/ */
......
...@@ -20,16 +20,19 @@ import java.io.File; ...@@ -20,16 +20,19 @@ import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection;
import java.util.List; import java.util.List;
import org.hamcrest.Description; import org.hamcrest.Description;
import org.hamcrest.TypeSafeMatcher; import org.hamcrest.TypeSafeMatcher;
import org.junit.Test; import org.junit.Test;
import org.springframework.boot.cli.command.jar.ResourceMatcher.MatchedResource; import org.springframework.boot.cli.command.jar.ResourceMatcher.MatchedResource;
import org.springframework.test.util.ReflectionTestUtils;
import static org.hamcrest.Matchers.hasItem; import static org.hamcrest.Matchers.hasItem;
import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.not;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThat; import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
...@@ -40,16 +43,26 @@ import static org.junit.Assert.assertTrue; ...@@ -40,16 +43,26 @@ import static org.junit.Assert.assertTrue;
*/ */
public class ResourceMatcherTests { public class ResourceMatcherTests {
private final ResourceMatcher resourceMatcher = new ResourceMatcher(Arrays.asList(
"alpha/**", "bravo/*", "*"), Arrays.asList(".*", "alpha/**/excluded"));
@Test @Test
public void nonExistentRoot() throws IOException { public void nonExistentRoot() throws IOException {
List<MatchedResource> matchedResources = this.resourceMatcher.find(Arrays ResourceMatcher resourceMatcher = new ResourceMatcher(Arrays.asList("alpha/**",
"bravo/*", "*"), Arrays.asList(".*", "alpha/**/excluded"));
List<MatchedResource> matchedResources = resourceMatcher.find(Arrays
.asList(new File("does-not-exist"))); .asList(new File("does-not-exist")));
assertEquals(0, matchedResources.size()); assertEquals(0, matchedResources.size());
} }
@SuppressWarnings("unchecked")
@Test
public void defaults() throws Exception {
ResourceMatcher resourceMatcher = new ResourceMatcher(Arrays.asList(""),
Arrays.asList(""));
assertTrue(((Collection<String>) ReflectionTestUtils.getField(resourceMatcher,
"includes")).contains("static/**"));
assertTrue(((Collection<String>) ReflectionTestUtils.getField(resourceMatcher,
"excludes")).contains("**/*.jar"));
}
@Test @Test
public void excludedWins() throws Exception { public void excludedWins() throws Exception {
ResourceMatcher resourceMatcher = new ResourceMatcher(Arrays.asList("*"), ResourceMatcher resourceMatcher = new ResourceMatcher(Arrays.asList("*"),
...@@ -59,6 +72,40 @@ public class ResourceMatcherTests { ...@@ -59,6 +72,40 @@ public class ResourceMatcherTests {
assertThat(found, not(hasItem(new FooJarMatcher(MatchedResource.class)))); assertThat(found, not(hasItem(new FooJarMatcher(MatchedResource.class))));
} }
@SuppressWarnings("unchecked")
@Test
public void includedDeltas() throws Exception {
ResourceMatcher resourceMatcher = new ResourceMatcher(
Arrays.asList("-static/**"), Arrays.asList(""));
Collection<String> includes = (Collection<String>) ReflectionTestUtils.getField(
resourceMatcher, "includes");
assertTrue(includes.contains("templates/**"));
assertFalse(includes.contains("static/**"));
}
@SuppressWarnings("unchecked")
@Test
public void includedDeltasAndNewEntries() throws Exception {
ResourceMatcher resourceMatcher = new ResourceMatcher(Arrays.asList("-static/**",
"foo.jar"), Arrays.asList("-**/*.jar"));
Collection<String> includes = (Collection<String>) ReflectionTestUtils.getField(
resourceMatcher, "includes");
assertTrue(includes.contains("foo.jar"));
assertTrue(includes.contains("templates/**"));
assertFalse(includes.contains("static/**"));
assertFalse(((Collection<String>) ReflectionTestUtils.getField(resourceMatcher,
"excludes")).contains("**/*.jar"));
}
@SuppressWarnings("unchecked")
@Test
public void excludedDeltas() throws Exception {
ResourceMatcher resourceMatcher = new ResourceMatcher(Arrays.asList(""),
Arrays.asList("-**/*.jar"));
assertFalse(((Collection<String>) ReflectionTestUtils.getField(resourceMatcher,
"excludes")).contains("**/*.jar"));
}
@Test @Test
public void jarFileAlwaysMatches() throws Exception { public void jarFileAlwaysMatches() throws Exception {
ResourceMatcher resourceMatcher = new ResourceMatcher(Arrays.asList("*"), ResourceMatcher resourceMatcher = new ResourceMatcher(Arrays.asList("*"),
...@@ -73,7 +120,9 @@ public class ResourceMatcherTests { ...@@ -73,7 +120,9 @@ public class ResourceMatcherTests {
@Test @Test
public void resourceMatching() throws IOException { public void resourceMatching() throws IOException {
List<MatchedResource> matchedResources = this.resourceMatcher.find(Arrays.asList( ResourceMatcher resourceMatcher = new ResourceMatcher(Arrays.asList("alpha/**",
"bravo/*", "*"), Arrays.asList(".*", "alpha/**/excluded"));
List<MatchedResource> matchedResources = resourceMatcher.find(Arrays.asList(
new File("src/test/resources/resource-matcher/one"), new File( new File("src/test/resources/resource-matcher/one"), new File(
"src/test/resources/resource-matcher/two"), new File( "src/test/resources/resource-matcher/two"), new File(
"src/test/resources/resource-matcher/three"))); "src/test/resources/resource-matcher/three")));
......
...@@ -265,9 +265,25 @@ executable jar file. For example: ...@@ -265,9 +265,25 @@ executable jar file. For example:
$ spring jar my-app.jar *.groovy $ spring jar my-app.jar *.groovy
---- ----
The resulting jar will contain the classes produced by compiling the application and all The resulting jar will contain the classes produced by compiling the
of the application's dependencies so that it can then be run using `java -jar`. The jar application and all of the application's dependencies so that it can
file will also contain entries from the application's classpath. then be run using `java -jar`. The jar file will also contain entries
from the application's classpath. You can add explicit paths to the
jar using `--include` and `--exclude` (both are comma separated, and
both accept prefixes to the values "+" and "-" to signify that they
shoudl be removed from the defaults). The default includes are
[indent=0]
----
public/**, resources/**, static/**, templates/**, META-INF/**, *
----
and the default excludes are
[indent=0]
----
.*, repository/**, build/**, target/**, **/*.jar, **/*.groovy
----
See the output of `spring help jar` for more information. See the output of `spring help jar` for more information.
......
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