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
This commit is contained in:
Dave Syer 2014-06-12 18:08:33 +01:00
parent d842446186
commit 2c691e5ae5
6 changed files with 142 additions and 20 deletions

View File

@ -26,6 +26,7 @@ import org.springframework.boot.loader.tools.JavaExecutable;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.not;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
@ -80,7 +81,32 @@ public class JarCommandIT {
assertThat(invocation.getStandardOutput(), containsString("/static/static.txt"));
assertThat(invocation.getStandardOutput(),
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(),
containsString("Goodbye Mama"));
not(containsString("/public/public.txt")));
assertThat(invocation.getStandardOutput(),
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"));
}
}

View File

@ -169,7 +169,9 @@ public class CommandRunner implements Iterable<Command> {
try {
ExitStatus result = run(argsWithoutDebugFlags);
// 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) {
showUsage();

View File

@ -68,12 +68,6 @@ import org.springframework.util.Assert;
*/
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();
public JarCommand() {
@ -98,11 +92,11 @@ public class JarCommand extends OptionParsingCommand {
this.includeOption = option(
"include",
"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(
"exclude",
"Pattern applied to directories on the claspath to find files to exclude from the resulting jar")
.withRequiredArg().defaultsTo(DEFAULT_EXCLUDES);
.withRequiredArg().withValuesSeparatedBy(",").defaultsTo("");
}
@Override

View File

@ -23,7 +23,9 @@ import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.FileSystemResource;
@ -39,6 +41,12 @@ import org.springframework.util.AntPathMatcher;
*/
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 List<String> includes;
@ -46,8 +54,8 @@ class ResourceMatcher {
private final List<String> excludes;
ResourceMatcher(List<String> includes, List<String> excludes) {
this.includes = includes;
this.excludes = excludes;
this.includes = getOptions(includes, DEFAULT_INCLUDES);
this.excludes = getOptions(excludes, DEFAULT_EXCLUDES);
}
public List<MatchedResource> find(List<File> roots) throws IOException {
@ -93,6 +101,33 @@ class ResourceMatcher {
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.
*/

View File

@ -20,16 +20,19 @@ import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import org.hamcrest.Description;
import org.hamcrest.TypeSafeMatcher;
import org.junit.Test;
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.not;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
@ -40,16 +43,26 @@ import static org.junit.Assert.assertTrue;
*/
public class ResourceMatcherTests {
private final ResourceMatcher resourceMatcher = new ResourceMatcher(Arrays.asList(
"alpha/**", "bravo/*", "*"), Arrays.asList(".*", "alpha/**/excluded"));
@Test
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")));
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
public void excludedWins() throws Exception {
ResourceMatcher resourceMatcher = new ResourceMatcher(Arrays.asList("*"),
@ -59,6 +72,40 @@ public class ResourceMatcherTests {
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
public void jarFileAlwaysMatches() throws Exception {
ResourceMatcher resourceMatcher = new ResourceMatcher(Arrays.asList("*"),
@ -73,7 +120,9 @@ public class ResourceMatcherTests {
@Test
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(
"src/test/resources/resource-matcher/two"), new File(
"src/test/resources/resource-matcher/three")));

View File

@ -265,9 +265,25 @@ executable jar file. For example:
$ spring jar my-app.jar *.groovy
----
The resulting jar will contain the classes produced by compiling the application and all
of the application's dependencies so that it can then be run using `java -jar`. The jar
file will also contain entries from the application's classpath.
The resulting jar will contain the classes produced by compiling the
application and all of the application's dependencies so that it can
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.