Add a command to produce a self-contained executable JAR for a CLI app

A new command, jar, has been added to the CLI. The command can be
used to create a self-contained executable JAR file from a CLI app.

Basic usage is:

spring jar <jar-name> <source-files>

For example:

spring jar my-app.jar *.groovy

The resulting jar will contain the classes generated by compiling the
source files, all of the application's dependencies, and entries
on the application's classpath.

By default a CLI application has the current working directory on
its classpath. This can be overridden using the --classpath option.
Any file that is referenced directly by the classpath is always
included in the jar. Any file that is found a result of being
contained within a directory that is on the classpath is subject to
filtering to determine whether or not it should be included. The
default includes are public/**, static/**, resources/**,
META-INF/**, *. The default excludes are .*, repository/**, build/**,
target/**. To be included in the jar, a file must match one of the
includes and none of the excludes. The filters can be overridden using
the --include and --exclude options.

Closes #241
This commit is contained in:
Andy Wilkinson
2014-01-29 14:00:31 +00:00
parent a99a38966f
commit 96e10104e4
27 changed files with 896 additions and 44 deletions

View File

@@ -43,7 +43,7 @@ public class CommandLineIT {
Invocation cli = this.cli.invoke("hint");
assertThat(cli.await(), equalTo(0));
assertThat(cli.getErrorOutput().length(), equalTo(0));
assertThat(cli.getStandardOutputLines().size(), equalTo(6));
assertThat(cli.getStandardOutputLines().size(), equalTo(7));
}
@Test

View File

@@ -0,0 +1,75 @@
/*
* Copyright 2012-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.cli;
import java.io.File;
import org.junit.Test;
import org.springframework.boot.cli.infrastructure.CommandLineInvoker;
import org.springframework.boot.cli.infrastructure.CommandLineInvoker.Invocation;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
/**
* @author Andy Wilkinson
*/
public class JarCommandIT {
private final CommandLineInvoker cli = new CommandLineInvoker(new File(
"src/it/resources/jar-command"));
@Test
public void noArguments() throws Exception {
Invocation invocation = this.cli.invoke("jar");
invocation.await();
assertEquals(0, invocation.getStandardOutput().length());
assertEquals(
"The name of the resulting jar and at least one source file must be specified",
invocation.getErrorOutput().trim());
}
@Test
public void noSources() throws Exception {
Invocation invocation = this.cli.invoke("jar", "test-app.jar");
invocation.await();
assertEquals(0, invocation.getStandardOutput().length());
assertEquals(
"The name of the resulting jar and at least one source file must be specified",
invocation.getErrorOutput().trim());
}
@Test
public void jarCreation() throws Exception {
File jar = new File("target/test-app.jar");
Invocation invocation = this.cli.invoke("jar", jar.getAbsolutePath(),
"jar.groovy");
invocation.await();
assertEquals(0, invocation.getErrorOutput().length());
assertTrue(jar.exists());
ProcessBuilder builder = new ProcessBuilder(System.getProperty("java.home")
+ "/bin/java", "-jar", jar.getAbsolutePath());
Process process = builder.start();
Invocation appInvocation = new Invocation(process);
appInvocation.await();
assertEquals(0, appInvocation.getErrorOutput().length());
assertTrue(appInvocation.getStandardOutput().contains("Hello World!"));
assertTrue(appInvocation.getStandardOutput().contains("/static/test.txt"));
}
}

View File

@@ -37,6 +37,16 @@ import org.springframework.util.Assert;
*/
public final class CommandLineInvoker {
private final File workingDirectory;
public CommandLineInvoker() {
this(new File("."));
}
public CommandLineInvoker(File workingDirectory) {
this.workingDirectory = workingDirectory;
}
public Invocation invoke(String... args) throws IOException {
return new Invocation(runCliProcess(args));
}
@@ -45,7 +55,7 @@ public final class CommandLineInvoker {
List<String> command = new ArrayList<String>();
command.add(findLaunchScript().getAbsolutePath());
command.addAll(Arrays.asList(args));
return new ProcessBuilder(command).start();
return new ProcessBuilder(command).directory(this.workingDirectory).start();
}
private File findLaunchScript() {
@@ -70,9 +80,9 @@ public final class CommandLineInvoker {
}
/**
* An ongoing CLI invocation.
* An ongoing Process invocation.
*/
public final class Invocation {
public static final class Invocation {
private final StringBuffer err = new StringBuffer();
@@ -80,7 +90,7 @@ public final class CommandLineInvoker {
private final Process process;
Invocation(Process process) {
public Invocation(Process process) {
this.process = process;
new Thread(new StreamReadingRunnable(this.process.getErrorStream(), this.err))
.start();

View File

@@ -0,0 +1,21 @@
package org.test
@Component
class Example implements CommandLineRunner {
@Autowired
private MyService myService
void run(String... args) {
println "Hello ${this.myService.sayWorld()}"
println getClass().getResource('/static/test.txt')
}
}
@Service
class MyService {
String sayWorld() {
return 'World!'
}
}