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