From 2c691e5ae54341e32b5104564e7f8db1d75461fb Mon Sep 17 00:00:00 2001 From: Dave Syer Date: Thu, 12 Jun 2014 18:08:33 +0100 Subject: [PATCH] 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 --- .../boot/cli/JarCommandIT.java | 28 ++++++++- .../boot/cli/command/CommandRunner.java | 4 +- .../boot/cli/command/jar/JarCommand.java | 10 +--- .../boot/cli/command/jar/ResourceMatcher.java | 39 +++++++++++- .../cli/command/jar/ResourceMatcherTests.java | 59 +++++++++++++++++-- .../src/main/asciidoc/spring-boot-cli.adoc | 22 ++++++- 6 files changed, 142 insertions(+), 20 deletions(-) diff --git a/spring-boot-cli/src/it/java/org/springframework/boot/cli/JarCommandIT.java b/spring-boot-cli/src/it/java/org/springframework/boot/cli/JarCommandIT.java index 04ef11ec8a2..6542e25cab7 100644 --- a/spring-boot-cli/src/it/java/org/springframework/boot/cli/JarCommandIT.java +++ b/spring-boot-cli/src/it/java/org/springframework/boot/cli/JarCommandIT.java @@ -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(), + not(containsString("/public/public.txt"))); 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")); } } diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/CommandRunner.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/CommandRunner.java index 8e43ab01b6f..8f78eb20ba0 100644 --- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/CommandRunner.java +++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/CommandRunner.java @@ -169,7 +169,9 @@ public class CommandRunner implements Iterable { 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(); diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/jar/JarCommand.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/jar/JarCommand.java index 4b5b744140b..89825113db6 100644 --- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/jar/JarCommand.java +++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/jar/JarCommand.java @@ -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 diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/jar/ResourceMatcher.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/jar/ResourceMatcher.java index 403cf2ebd8b..a6ff6b44632 100644 --- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/jar/ResourceMatcher.java +++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/jar/ResourceMatcher.java @@ -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 includes; @@ -46,8 +54,8 @@ class ResourceMatcher { private final List excludes; ResourceMatcher(List includes, List excludes) { - this.includes = includes; - this.excludes = excludes; + this.includes = getOptions(includes, DEFAULT_INCLUDES); + this.excludes = getOptions(excludes, DEFAULT_EXCLUDES); } public List find(List roots) throws IOException { @@ -93,6 +101,33 @@ class ResourceMatcher { return false; } + private List getOptions(List values, String[] defaults) { + Set result = new LinkedHashSet(); + Set minus = new LinkedHashSet(); + 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(result); + } + /** * {@link ResourceLoader} to get load resource from a folder. */ diff --git a/spring-boot-cli/src/test/java/org/springframework/boot/cli/command/jar/ResourceMatcherTests.java b/spring-boot-cli/src/test/java/org/springframework/boot/cli/command/jar/ResourceMatcherTests.java index 8a242addf5d..d344311fcd9 100644 --- a/spring-boot-cli/src/test/java/org/springframework/boot/cli/command/jar/ResourceMatcherTests.java +++ b/spring-boot-cli/src/test/java/org/springframework/boot/cli/command/jar/ResourceMatcherTests.java @@ -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 matchedResources = this.resourceMatcher.find(Arrays + ResourceMatcher resourceMatcher = new ResourceMatcher(Arrays.asList("alpha/**", + "bravo/*", "*"), Arrays.asList(".*", "alpha/**/excluded")); + List 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) ReflectionTestUtils.getField(resourceMatcher, + "includes")).contains("static/**")); + assertTrue(((Collection) 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 includes = (Collection) 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 includes = (Collection) ReflectionTestUtils.getField( + resourceMatcher, "includes"); + assertTrue(includes.contains("foo.jar")); + assertTrue(includes.contains("templates/**")); + assertFalse(includes.contains("static/**")); + assertFalse(((Collection) 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) 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 matchedResources = this.resourceMatcher.find(Arrays.asList( + ResourceMatcher resourceMatcher = new ResourceMatcher(Arrays.asList("alpha/**", + "bravo/*", "*"), Arrays.asList(".*", "alpha/**/excluded")); + List 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"))); diff --git a/spring-boot-docs/src/main/asciidoc/spring-boot-cli.adoc b/spring-boot-docs/src/main/asciidoc/spring-boot-cli.adoc index 31b1b44166e..0837a9ea435 100644 --- a/spring-boot-docs/src/main/asciidoc/spring-boot-cli.adoc +++ b/spring-boot-docs/src/main/asciidoc/spring-boot-cli.adoc @@ -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.