Polish CLI Jar generation

This commit is contained in:
Phillip Webb 2014-01-29 17:18:11 -08:00
parent d648603634
commit 208bf8fc96
9 changed files with 278 additions and 252 deletions

View File

@ -103,12 +103,6 @@
<artifactId>aether-util</artifactId> <artifactId>aether-util</artifactId>
</dependency> </dependency>
<!-- Provided --> <!-- Provided -->
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>spring-boot-loader</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency> <dependency>
<groupId>org.codehaus.groovy</groupId> <groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-templates</artifactId> <artifactId>groovy-templates</artifactId>

View File

@ -19,13 +19,20 @@ package org.springframework.boot.cli;
import java.io.File; import java.io.File;
import org.junit.Test; import org.junit.Test;
import org.springframework.boot.cli.command.jar.JarCommand;
import org.springframework.boot.cli.infrastructure.CommandLineInvoker; import org.springframework.boot.cli.infrastructure.CommandLineInvoker;
import org.springframework.boot.cli.infrastructure.CommandLineInvoker.Invocation; import org.springframework.boot.cli.infrastructure.CommandLineInvoker.Invocation;
import org.springframework.boot.cli.util.JavaExecutable;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
/** /**
* Integration test for {@link JarCommand}.
*
* @author Andy Wilkinson * @author Andy Wilkinson
*/ */
public class JarCommandIT { public class JarCommandIT {
@ -37,20 +44,18 @@ public class JarCommandIT {
public void noArguments() throws Exception { public void noArguments() throws Exception {
Invocation invocation = this.cli.invoke("jar"); Invocation invocation = this.cli.invoke("jar");
invocation.await(); invocation.await();
assertEquals(0, invocation.getStandardOutput().length()); assertThat(invocation.getStandardOutput(), equalTo(""));
assertEquals( assertThat(invocation.getErrorOutput(), containsString("The name of the "
"The name of the resulting jar and at least one source file must be specified", + "resulting jar and at least one source file must be specified"));
invocation.getErrorOutput().trim());
} }
@Test @Test
public void noSources() throws Exception { public void noSources() throws Exception {
Invocation invocation = this.cli.invoke("jar", "test-app.jar"); Invocation invocation = this.cli.invoke("jar", "test-app.jar");
invocation.await(); invocation.await();
assertEquals(0, invocation.getStandardOutput().length()); assertThat(invocation.getStandardOutput(), equalTo(""));
assertEquals( assertThat(invocation.getErrorOutput(), containsString("The name of the "
"The name of the resulting jar and at least one source file must be specified", + "resulting jar and at least one source file must be specified"));
invocation.getErrorOutput().trim());
} }
@Test @Test
@ -62,14 +67,13 @@ public class JarCommandIT {
assertEquals(0, invocation.getErrorOutput().length()); assertEquals(0, invocation.getErrorOutput().length());
assertTrue(jar.exists()); assertTrue(jar.exists());
ProcessBuilder builder = new ProcessBuilder(System.getProperty("java.home") Process process = new JavaExecutable().processBuilder("-jar",
+ "/bin/java", "-jar", jar.getAbsolutePath()); jar.getAbsolutePath()).start();
Process process = builder.start(); invocation = new Invocation(process);
Invocation appInvocation = new Invocation(process); invocation.await();
appInvocation.await();
assertEquals(0, appInvocation.getErrorOutput().length()); assertThat(invocation.getErrorOutput(), equalTo(""));
assertTrue(appInvocation.getStandardOutput().contains("Hello World!")); assertThat(invocation.getStandardOutput(), containsString("Hello World!"));
assertTrue(appInvocation.getStandardOutput().contains("/static/test.txt")); assertThat(invocation.getStandardOutput(), containsString("/static/test.txt"));
} }
} }

View File

@ -18,20 +18,17 @@ package org.springframework.boot.cli.command.jar;
import groovy.lang.Grab; import groovy.lang.Grab;
import java.io.ByteArrayInputStream;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.net.URI; import java.net.URI;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.net.URL; import java.net.URL;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.jar.Manifest; import java.util.jar.Manifest;
import joptsimple.OptionSet; import joptsimple.OptionSet;
@ -50,7 +47,6 @@ import org.springframework.boot.cli.command.OptionParsingCommand;
import org.springframework.boot.cli.command.SourceOptions; import org.springframework.boot.cli.command.SourceOptions;
import org.springframework.boot.cli.command.jar.ResourceMatcher.MatchedResource; import org.springframework.boot.cli.command.jar.ResourceMatcher.MatchedResource;
import org.springframework.boot.cli.compiler.GroovyCompiler; import org.springframework.boot.cli.compiler.GroovyCompiler;
import org.springframework.boot.cli.compiler.GroovyCompiler.CompilationCallback;
import org.springframework.boot.cli.compiler.GroovyCompilerConfiguration; import org.springframework.boot.cli.compiler.GroovyCompilerConfiguration;
import org.springframework.boot.cli.compiler.GroovyCompilerConfigurationAdapter; import org.springframework.boot.cli.compiler.GroovyCompilerConfigurationAdapter;
import org.springframework.boot.cli.compiler.RepositoryConfigurationFactory; import org.springframework.boot.cli.compiler.RepositoryConfigurationFactory;
@ -59,12 +55,13 @@ import org.springframework.boot.cli.jar.PackagedSpringApplicationLauncher;
import org.springframework.boot.loader.tools.JarWriter; import org.springframework.boot.loader.tools.JarWriter;
import org.springframework.boot.loader.tools.Layout; import org.springframework.boot.loader.tools.Layout;
import org.springframework.boot.loader.tools.Layouts; import org.springframework.boot.loader.tools.Layouts;
import org.springframework.util.StringUtils; import org.springframework.util.Assert;
/** /**
* {@link Command} to create a self-contained executable jar file from a CLI application * {@link Command} to create a self-contained executable jar file from a CLI application
* *
* @author Andy Wilkinson * @author Andy Wilkinson
* @author Phillip Webb
*/ */
public class JarCommand extends OptionParsingCommand { public class JarCommand extends OptionParsingCommand {
@ -77,9 +74,8 @@ public class JarCommand extends OptionParsingCommand {
private static final Layout LAYOUT = new Layouts.Jar(); private static final Layout LAYOUT = new Layouts.Jar();
public JarCommand() { public JarCommand() {
super( super("jar", "Create a self-contained "
"jar", + "executable jar file from a Spring Groovy script",
"Create a self-contained executable jar file from a Spring Groovy script",
new JarOptionHandler()); new JarOptionHandler());
} }
@ -110,156 +106,163 @@ public class JarCommand extends OptionParsingCommand {
protected void run(OptionSet options) throws Exception { protected void run(OptionSet options) throws Exception {
List<?> nonOptionArguments = new ArrayList<Object>( List<?> nonOptionArguments = new ArrayList<Object>(
options.nonOptionArguments()); options.nonOptionArguments());
if (nonOptionArguments.size() < 2) { Assert.isTrue(nonOptionArguments.size() >= 2,
throw new IllegalStateException( "The name of the resulting jar and at least one source file must be specified");
"The name of the resulting jar and at least one source file must be specified");
}
File output = new File((String) nonOptionArguments.remove(0)); File output = new File((String) nonOptionArguments.remove(0));
if (output.exists() && !output.delete()) { deleteIfExists(output);
throw new IllegalStateException(
"Failed to delete existing application jar file "
+ output.getPath());
}
GroovyCompiler groovyCompiler = createCompiler(options); GroovyCompiler compiler = createCompiler(options);
List<URL> classpathUrls = Arrays.asList(groovyCompiler.getLoader().getURLs()); List<URL> classpath = getClassPathUrls(compiler);
List<MatchedResource> classpathEntries = findClasspathEntries(classpathUrls, List<MatchedResource> classpathEntries = findMatchingClasspathEntries(
options); classpath, options);
final Map<String, byte[]> compiledClasses = new HashMap<String, byte[]>(); String[] sources = new SourceOptions(nonOptionArguments).getSourcesArray();
groovyCompiler.compile(new CompilationCallback() { Class<?>[] compiledClasses = compiler.compile(sources);
@Override List<URL> dependencies = getClassPathUrls(compiler);
public void byteCodeGenerated(byte[] byteCode, ClassNode classNode) dependencies.removeAll(classpath);
throws IOException {
String className = classNode.getName();
compiledClasses.put(className, byteCode);
}
}, new SourceOptions(nonOptionArguments).getSourcesArray()); writeJar(output, compiledClasses, classpathEntries, dependencies);
}
List<URL> dependencyUrls = new ArrayList<URL>(Arrays.asList(groovyCompiler private void deleteIfExists(File file) {
.getLoader().getURLs())); if (file.exists() && !file.delete()) {
dependencyUrls.removeAll(classpathUrls); throw new IllegalStateException("Failed to delete existing file "
+ file.getPath());
JarWriter jarWriter = new JarWriter(output);
try {
jarWriter.writeManifest(createManifest(compiledClasses));
addDependencies(jarWriter, dependencyUrls);
addClasspathEntries(jarWriter, classpathEntries);
addApplicationClasses(jarWriter, compiledClasses);
String runnerClassName = getClassFile(PackagedSpringApplicationLauncher.class
.getName());
jarWriter.writeEntry(runnerClassName,
getClass().getResourceAsStream("/" + runnerClassName));
jarWriter.writeLoaderClasses();
}
finally {
jarWriter.close();
} }
} }
private GroovyCompiler createCompiler(OptionSet options) { private GroovyCompiler createCompiler(OptionSet options) {
List<RepositoryConfiguration> repositoryConfiguration = RepositoryConfigurationFactory List<RepositoryConfiguration> repositoryConfiguration = RepositoryConfigurationFactory
.createDefaultRepositoryConfiguration(); .createDefaultRepositoryConfiguration();
GroovyCompilerConfiguration configuration = new GroovyCompilerConfigurationAdapter( GroovyCompilerConfiguration configuration = new GroovyCompilerConfigurationAdapter(
options, this, repositoryConfiguration); options, this, repositoryConfiguration);
GroovyCompiler groovyCompiler = new GroovyCompiler(configuration); GroovyCompiler groovyCompiler = new GroovyCompiler(configuration);
groovyCompiler.getAstTransformations().add(0, new ASTTransformation() { groovyCompiler.getAstTransformations().add(0, new GrabAnnotationTransform());
@Override
public void visit(ASTNode[] nodes, SourceUnit source) {
for (ASTNode node : nodes) {
if (node instanceof ModuleNode) {
ModuleNode module = (ModuleNode) node;
for (ClassNode classNode : module.getClasses()) {
AnnotationNode annotation = new AnnotationNode(
new ClassNode(Grab.class));
annotation.addMember("value", new ConstantExpression(
"groovy"));
classNode.addAnnotation(annotation);
}
}
}
}
});
return groovyCompiler; return groovyCompiler;
} }
private List<MatchedResource> findClasspathEntries(List<URL> classpath, private List<URL> getClassPathUrls(GroovyCompiler compiler) {
return new ArrayList<URL>(Arrays.asList(compiler.getLoader().getURLs()));
}
private List<MatchedResource> findMatchingClasspathEntries(List<URL> classpath,
OptionSet options) throws IOException { OptionSet options) throws IOException {
ResourceMatcher resourceCollector = new ResourceMatcher( ResourceMatcher matcher = new ResourceMatcher(
options.valuesOf(this.includeOption), options.valuesOf(this.includeOption),
options.valuesOf(this.excludeOption)); options.valuesOf(this.excludeOption));
List<File> roots = new ArrayList<File>(); List<File> roots = new ArrayList<File>();
for (URL classpathEntry : classpath) { for (URL classpathEntry : classpath) {
roots.add(new File(URI.create(classpathEntry.toString()))); roots.add(new File(URI.create(classpathEntry.toString())));
} }
return matcher.find(roots);
return resourceCollector.matchResources(roots);
} }
private Manifest createManifest(final Map<String, byte[]> compiledClasses) { private void writeJar(File file, Class<?>[] compiledClasses,
List<MatchedResource> classpathEntries, List<URL> dependencies)
throws FileNotFoundException, IOException, URISyntaxException {
JarWriter writer = new JarWriter(file);
try {
addManifest(writer, compiledClasses);
addCliClasses(writer);
for (Class<?> compiledClass : compiledClasses) {
addClass(writer, compiledClass);
}
addClasspathEntries(writer, classpathEntries);
addDependencies(writer, dependencies);
writer.writeLoaderClasses();
}
finally {
writer.close();
}
}
private void addManifest(JarWriter writer, Class<?>[] compiledClasses)
throws IOException {
Manifest manifest = new Manifest(); Manifest manifest = new Manifest();
manifest.getMainAttributes().putValue("Manifest-Version", "1.0"); manifest.getMainAttributes().putValue("Manifest-Version", "1.0");
manifest.getMainAttributes()
.putValue(
"Application-Classes",
StringUtils.collectionToCommaDelimitedString(compiledClasses
.keySet()));
manifest.getMainAttributes().putValue("Main-Class", manifest.getMainAttributes().putValue("Main-Class",
LAYOUT.getLauncherClassName()); LAYOUT.getLauncherClassName());
manifest.getMainAttributes().putValue("Start-Class", manifest.getMainAttributes().putValue("Start-Class",
PackagedSpringApplicationLauncher.class.getName()); PackagedSpringApplicationLauncher.class.getName());
return manifest; manifest.getMainAttributes().putValue(
PackagedSpringApplicationLauncher.SOURCE_MANIFEST_ENTRY,
commaDelimitedClassNames(compiledClasses));
writer.writeManifest(manifest);
} }
private void addDependencies(JarWriter jarWriter, List<URL> urls) private String commaDelimitedClassNames(Class<?>[] classes) {
throws IOException, URISyntaxException, FileNotFoundException { StringBuilder builder = new StringBuilder();
for (URL url : urls) { for (int i = 0; i < classes.length; i++) {
addDependency(jarWriter, new File(url.toURI())); builder.append(i == 0 ? "" : ",");
builder.append(classes[i].getName());
} }
return builder.toString();
} }
private void addDependency(JarWriter jarWriter, File dependency) private void addCliClasses(JarWriter writer) throws IOException {
throws FileNotFoundException, IOException { addClass(writer, PackagedSpringApplicationLauncher.class);
if (dependency.isFile()) {
jarWriter.writeNestedLibrary("lib/", dependency);
}
} }
private void addClasspathEntries(JarWriter jarWriter, private void addClass(JarWriter writer, Class<?> sourceClass) throws IOException {
List<MatchedResource> classpathEntries) throws IOException { String name = sourceClass.getName().replace(".", "/") + ".class";
for (MatchedResource classpathEntry : classpathEntries) { InputStream stream = sourceClass.getResourceAsStream("/" + name);
if (classpathEntry.isRoot()) { writer.writeEntry(name, stream);
addDependency(jarWriter, classpathEntry.getFile()); }
private void addClasspathEntries(JarWriter writer, List<MatchedResource> entries)
throws IOException {
for (MatchedResource entry : entries) {
if (entry.isRoot()) {
addDependency(writer, entry.getFile());
} }
else { else {
jarWriter.writeEntry(classpathEntry.getPath(), new FileInputStream( writer.writeEntry(entry.getName(),
classpathEntry.getFile())); new FileInputStream(entry.getFile()));
} }
} }
} }
private void addApplicationClasses(JarWriter jarWriter, private void addDependencies(JarWriter writer, List<URL> urls)
final Map<String, byte[]> compiledClasses) throws IOException { throws IOException, URISyntaxException, FileNotFoundException {
for (URL url : urls) {
for (Entry<String, byte[]> entry : compiledClasses.entrySet()) { addDependency(writer, new File(url.toURI()));
jarWriter.writeEntry(getClassFile(entry.getKey()),
new ByteArrayInputStream(entry.getValue()));
} }
} }
private String getClassFile(String className) { private void addDependency(JarWriter writer, File dependency)
return className.replace(".", "/") + ".class"; throws FileNotFoundException, IOException {
if (dependency.isFile()) {
writer.writeNestedLibrary("lib/", dependency);
}
} }
} }
/**
* {@link ASTTransformation} to change {@code @Grab} annotation values.
*/
private static class GrabAnnotationTransform implements ASTTransformation {
@Override
public void visit(ASTNode[] nodes, SourceUnit source) {
for (ASTNode node : nodes) {
if (node instanceof ModuleNode) {
visitModule((ModuleNode) node);
}
}
}
private void visitModule(ModuleNode module) {
for (ClassNode classNode : module.getClasses()) {
AnnotationNode annotation = new AnnotationNode(new ClassNode(Grab.class));
annotation.addMember("value", new ConstantExpression("groovy"));
classNode.addAnnotation(annotation);
}
}
}
} }

View File

@ -28,6 +28,7 @@ import java.util.List;
import org.springframework.core.io.DefaultResourceLoader; import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.FileSystemResource; import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource; import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver; import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.util.AntPathMatcher; import org.springframework.util.AntPathMatcher;
@ -36,7 +37,7 @@ import org.springframework.util.AntPathMatcher;
* *
* @author Andy Wilkinson * @author Andy Wilkinson
*/ */
final class ResourceMatcher { class ResourceMatcher {
private final AntPathMatcher pathMatcher = new AntPathMatcher(); private final AntPathMatcher pathMatcher = new AntPathMatcher();
@ -49,110 +50,128 @@ final class ResourceMatcher {
this.excludes = excludes; this.excludes = excludes;
} }
List<MatchedResource> matchResources(List<File> roots) throws IOException { public List<MatchedResource> find(List<File> roots) throws IOException {
List<MatchedResource> matchedResources = new ArrayList<MatchedResource>(); List<MatchedResource> matchedResources = new ArrayList<MatchedResource>();
for (File root : roots) { for (File root : roots) {
if (root.isFile()) { if (root.isFile()) {
matchedResources.add(new MatchedResource(root)); matchedResources.add(new MatchedResource(root));
} }
else { else {
matchedResources.addAll(matchResources(root)); matchedResources.addAll(findInFolder(root));
} }
} }
return matchedResources; return matchedResources;
} }
private List<MatchedResource> matchResources(File root) throws IOException { private List<MatchedResource> findInFolder(File folder) throws IOException {
List<MatchedResource> resources = new ArrayList<MatchedResource>(); List<MatchedResource> matchedResources = new ArrayList<MatchedResource>();
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver( PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(
new ResourceCollectionResourceLoader(root)); new FolderResourceLoader(folder));
for (String include : this.includes) { for (String include : this.includes) {
Resource[] candidates = resolver.getResources(include); for (Resource candidate : resolver.getResources(include)) {
for (Resource candidate : candidates) {
File file = candidate.getFile(); File file = candidate.getFile();
if (file.isFile()) { if (file.isFile()) {
MatchedResource matchedResource = new MatchedResource(root, file); MatchedResource matchedResource = new MatchedResource(folder, file);
if (!isExcluded(matchedResource)) { if (!isExcluded(matchedResource)) {
resources.add(matchedResource); matchedResources.add(matchedResource);
} }
} }
} }
} }
return resources; return matchedResources;
} }
private boolean isExcluded(MatchedResource matchedResource) { private boolean isExcluded(MatchedResource matchedResource) {
for (String exclude : this.excludes) { for (String exclude : this.excludes) {
if (this.pathMatcher.match(exclude, matchedResource.getPath())) { if (this.pathMatcher.match(exclude, matchedResource.getName())) {
return true; return true;
} }
} }
return false; return false;
} }
private static final class ResourceCollectionResourceLoader extends /**
DefaultResourceLoader { * {@link ResourceLoader} to get load resource from a folder.
*/
private static class FolderResourceLoader extends DefaultResourceLoader {
private final File root; private final File rootFolder;
ResourceCollectionResourceLoader(File root) throws MalformedURLException { public FolderResourceLoader(File root) throws MalformedURLException {
super(new URLClassLoader(new URL[] { root.toURI().toURL() }) { super(new FolderClassLoader(root));
@Override this.rootFolder = root;
public Enumeration<URL> getResources(String name) throws IOException {
return findResources(name);
}
@Override
public URL getResource(String name) {
return findResource(name);
}
});
this.root = root;
} }
@Override @Override
protected Resource getResourceByPath(String path) { protected Resource getResourceByPath(String path) {
return new FileSystemResource(new File(this.root, path)); return new FileSystemResource(new File(this.rootFolder, path));
} }
} }
static final class MatchedResource { /**
* {@link ClassLoader} backed by a folder.
*/
private static class FolderClassLoader extends URLClassLoader {
public FolderClassLoader(File rootFolder) throws MalformedURLException {
super(new URL[] { rootFolder.toURI().toURL() });
}
@Override
public Enumeration<URL> getResources(String name) throws IOException {
return findResources(name);
}
@Override
public URL getResource(String name) {
return findResource(name);
}
}
/**
* A single matched resource.
*/
public static final class MatchedResource {
private final File file; private final File file;
private final String path; private final String name;
private final boolean root; private final boolean root;
private MatchedResource(File resourceFile) { private MatchedResource(File file) {
this(resourceFile, resourceFile.getName(), true); this.name = file.getName();
this.file = file;
this.root = false;
} }
private MatchedResource(File root, File resourceFile) { private MatchedResource(File rootFolder, File file) {
this(resourceFile, resourceFile.getAbsolutePath().substring( this.name = file.getAbsolutePath().substring(
root.getAbsolutePath().length() + 1), false); rootFolder.getAbsolutePath().length() + 1);
this.file = file;
this.root = false;
} }
private MatchedResource(File resourceFile, String path, boolean root) { private MatchedResource(File resourceFile, String path, boolean root) {
this.file = resourceFile; this.file = resourceFile;
this.path = path; this.name = path;
this.root = root; this.root = root;
} }
File getFile() { public String getName() {
return this.name;
}
public File getFile() {
return this.file; return this.file;
} }
String getPath() { public boolean isRoot() {
return this.path;
}
boolean isRoot() {
return this.root; return this.root;
} }
@ -160,6 +179,7 @@ final class ResourceMatcher {
public String toString() { public String toString() {
return this.file.getAbsolutePath(); return this.file.getAbsolutePath();
} }
} }
} }

View File

@ -16,8 +16,6 @@
package org.springframework.boot.cli.command.shell; package org.springframework.boot.cli.command.shell;
import java.io.File;
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.Collection;
@ -25,8 +23,7 @@ import java.util.List;
import org.springframework.boot.cli.command.Command; import org.springframework.boot.cli.command.Command;
import org.springframework.boot.cli.command.OptionHelp; import org.springframework.boot.cli.command.OptionHelp;
import org.springframework.util.Assert; import org.springframework.boot.cli.util.JavaExecutable;
import org.springframework.util.StringUtils;
/** /**
* Decorate an existing command to run it by forking the current java process. * Decorate an existing command to run it by forking the current java process.
@ -40,7 +37,7 @@ class ForkProcessCommand extends RunProcessCommand {
private final Command command; private final Command command;
public ForkProcessCommand(Command command) { public ForkProcessCommand(Command command) {
super(getJavaCommand()); super(new JavaExecutable().toString());
this.command = command; this.command = command;
} }
@ -80,24 +77,4 @@ class ForkProcessCommand extends RunProcessCommand {
run(fullArgs); run(fullArgs);
} }
private static String getJavaCommand() {
String javaHome = System.getProperty("java.home");
Assert.state(StringUtils.hasLength(javaHome),
"Unable to find java command to fork process");
try {
return getJavaCommand(javaHome).getCanonicalPath();
}
catch (IOException ex) {
throw new IllegalStateException(ex);
}
}
private static File getJavaCommand(String javaHome) {
File bin = new File(new File(javaHome), "bin");
File command = new File(bin, "java.exe");
command = (command.exists() ? command : new File(bin, "java"));
Assert.state(command.exists(), "Unable to find java in " + javaHome);
return command;
}
} }

View File

@ -34,7 +34,6 @@ import org.codehaus.groovy.ast.ClassNode;
import org.codehaus.groovy.classgen.GeneratorContext; import org.codehaus.groovy.classgen.GeneratorContext;
import org.codehaus.groovy.control.CompilationFailedException; import org.codehaus.groovy.control.CompilationFailedException;
import org.codehaus.groovy.control.CompilationUnit; import org.codehaus.groovy.control.CompilationUnit;
import org.codehaus.groovy.control.CompilationUnit.ClassgenCallback;
import org.codehaus.groovy.control.CompilePhase; import org.codehaus.groovy.control.CompilePhase;
import org.codehaus.groovy.control.CompilerConfiguration; import org.codehaus.groovy.control.CompilerConfiguration;
import org.codehaus.groovy.control.Phases; import org.codehaus.groovy.control.Phases;
@ -43,8 +42,6 @@ import org.codehaus.groovy.control.customizers.CompilationCustomizer;
import org.codehaus.groovy.control.customizers.ImportCustomizer; import org.codehaus.groovy.control.customizers.ImportCustomizer;
import org.codehaus.groovy.transform.ASTTransformation; import org.codehaus.groovy.transform.ASTTransformation;
import org.codehaus.groovy.transform.ASTTransformationVisitor; import org.codehaus.groovy.transform.ASTTransformationVisitor;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.springframework.boot.cli.compiler.grape.AetherGrapeEngine; import org.springframework.boot.cli.compiler.grape.AetherGrapeEngine;
import org.springframework.boot.cli.compiler.grape.AetherGrapeEngineFactory; import org.springframework.boot.cli.compiler.grape.AetherGrapeEngineFactory;
import org.springframework.boot.cli.compiler.grape.GrapeEngineInstaller; import org.springframework.boot.cli.compiler.grape.GrapeEngineInstaller;
@ -120,6 +117,10 @@ public class GroovyCompiler {
} }
} }
/**
* Return a mutable list of the {@link ASTTransformation}s to be applied during
* {@link #compile(String...)}.
*/
public List<ASTTransformation> getAstTransformations() { public List<ASTTransformation> getAstTransformations() {
return this.transformations; return this.transformations;
} }
@ -210,42 +211,6 @@ public class GroovyCompiler {
return classes.toArray(new Class<?>[classes.size()]); return classes.toArray(new Class<?>[classes.size()]);
} }
public void compile(final CompilationCallback callback, String... sources)
throws CompilationFailedException, IOException {
this.loader.clearCache();
CompilerConfiguration configuration = this.loader.getConfiguration();
final CompilationUnit compilationUnit = new CompilationUnit(configuration, null,
this.loader);
ClassgenCallback classgenCallback = new ClassgenCallback() {
@Override
public void call(ClassVisitor writer, ClassNode node)
throws CompilationFailedException {
try {
callback.byteCodeGenerated(((ClassWriter) writer).toByteArray(), node);
}
catch (IOException ioe) {
throw new CompilationFailedException(Phases.CLASS_GENERATION,
compilationUnit);
}
}
};
compilationUnit.setClassgenCallback(classgenCallback);
for (String source : sources) {
List<String> paths = ResourceUtils.getUrls(source, this.loader);
for (String path : paths) {
compilationUnit.addSource(new URL(path));
}
}
addAstTransformations(compilationUnit);
compilationUnit.compile(Phases.CLASS_GENERATION);
}
@SuppressWarnings("rawtypes") @SuppressWarnings("rawtypes")
private void addAstTransformations(CompilationUnit compilationUnit) { private void addAstTransformations(CompilationUnit compilationUnit) {
LinkedList[] phaseOperations = getPhaseOperations(compilationUnit); LinkedList[] phaseOperations = getPhaseOperations(compilationUnit);
@ -329,10 +294,4 @@ public class GroovyCompiler {
} }
public static interface CompilationCallback {
public void byteCodeGenerated(byte[] byteCode, ClassNode classNode)
throws IOException;
}
} }

View File

@ -29,6 +29,8 @@ import java.util.jar.Manifest;
*/ */
public class PackagedSpringApplicationLauncher { public class PackagedSpringApplicationLauncher {
public static final String SOURCE_MANIFEST_ENTRY = "Spring-Application-Source-Classes";
private static final String SPRING_APPLICATION_CLASS = "org.springframework.boot.SpringApplication"; private static final String SPRING_APPLICATION_CLASS = "org.springframework.boot.SpringApplication";
private void run(String[] args) throws Exception { private void run(String[] args) throws Exception {
@ -42,7 +44,7 @@ public class PackagedSpringApplicationLauncher {
private Object[] getSources(URLClassLoader classLoader) throws Exception { private Object[] getSources(URLClassLoader classLoader) throws Exception {
URL url = classLoader.findResource("META-INF/MANIFEST.MF"); URL url = classLoader.findResource("META-INF/MANIFEST.MF");
Manifest manifest = new Manifest(url.openStream()); Manifest manifest = new Manifest(url.openStream());
String attribute = manifest.getMainAttributes().getValue("Application-Classes"); String attribute = manifest.getMainAttributes().getValue(SOURCE_MANIFEST_ENTRY);
return loadClasses(classLoader, attribute.split(",")); return loadClasses(classLoader, attribute.split(","));
} }

View File

@ -0,0 +1,66 @@
/*
* 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.util;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* Provides access to the java binary executable, regardless of OS.
*
* @author Phillip Webb
*/
public class JavaExecutable {
private File file;
public JavaExecutable() {
String javaHome = System.getProperty("java.home");
Assert.state(StringUtils.hasLength(javaHome),
"Unable to find java executable due to missing 'java.home'");
this.file = findInJavaHome(javaHome);
}
private File findInJavaHome(String javaHome) {
File bin = new File(new File(javaHome), "bin");
File command = new File(bin, "java.exe");
command = (command.exists() ? command : new File(bin, "java"));
Assert.state(command.exists(), "Unable to find java in " + javaHome);
return command;
}
public ProcessBuilder processBuilder(String... arguments) {
ProcessBuilder processBuilder = new ProcessBuilder(toString());
processBuilder.command().addAll(Arrays.asList(arguments));
return processBuilder;
}
@Override
public String toString() {
try {
return this.file.getCanonicalPath();
}
catch (IOException ex) {
throw new IllegalStateException(ex);
}
}
}

View File

@ -29,7 +29,9 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
/** /**
* @author awilkinson * Tests for {@link ResourceMatcher}.
*
* @author Andy Wilkinson
*/ */
public class ResourceMatcherTests { public class ResourceMatcherTests {
@ -38,22 +40,21 @@ public class ResourceMatcherTests {
@Test @Test
public void nonExistentRoot() throws IOException { public void nonExistentRoot() throws IOException {
List<MatchedResource> matchedResources = this.resourceMatcher List<MatchedResource> matchedResources = this.resourceMatcher.find(Arrays
.matchResources(Arrays.asList(new File("does-not-exist"))); .asList(new File("does-not-exist")));
assertEquals(0, matchedResources.size()); assertEquals(0, matchedResources.size());
} }
@Test @Test
public void resourceMatching() throws IOException { public void resourceMatching() throws IOException {
List<MatchedResource> matchedResources = this.resourceMatcher List<MatchedResource> matchedResources = this.resourceMatcher.find(Arrays.asList(
.matchResources(Arrays.asList(new File( new File("src/test/resources/resource-matcher/one"), 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")));
System.out.println(matchedResources); System.out.println(matchedResources);
List<String> paths = new ArrayList<String>(); List<String> paths = new ArrayList<String>();
for (MatchedResource resource : matchedResources) { for (MatchedResource resource : matchedResources) {
paths.add(resource.getPath()); paths.add(resource.getName());
} }
assertEquals(6, paths.size()); assertEquals(6, paths.size());