From 208bf8fc96242ad5b4e18b163fe21631569beab6 Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Wed, 29 Jan 2014 17:18:11 -0800 Subject: [PATCH] Polish CLI Jar generation --- spring-boot-cli/pom.xml | 6 - .../boot/cli/JarCommandIT.java | 36 +-- .../boot/cli/command/jar/JarCommand.java | 217 +++++++++--------- .../boot/cli/command/jar/ResourceMatcher.java | 110 +++++---- .../cli/command/shell/ForkProcessCommand.java | 27 +-- .../boot/cli/compiler/GroovyCompiler.java | 49 +--- .../PackagedSpringApplicationLauncher.java | 4 +- .../boot/cli/util/JavaExecutable.java | 66 ++++++ .../cli/command/jar/ResourceMatcherTests.java | 15 +- 9 files changed, 278 insertions(+), 252 deletions(-) create mode 100644 spring-boot-cli/src/main/java/org/springframework/boot/cli/util/JavaExecutable.java diff --git a/spring-boot-cli/pom.xml b/spring-boot-cli/pom.xml index 43c2f6a8904..b4b31c0e511 100644 --- a/spring-boot-cli/pom.xml +++ b/spring-boot-cli/pom.xml @@ -103,12 +103,6 @@ aether-util - - ${project.groupId} - spring-boot-loader - ${project.version} - provided - org.codehaus.groovy groovy-templates 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 75b794ca96a..96b30958825 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 @@ -19,13 +19,20 @@ package org.springframework.boot.cli; import java.io.File; 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.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.assertThat; import static org.junit.Assert.assertTrue; /** + * Integration test for {@link JarCommand}. + * * @author Andy Wilkinson */ public class JarCommandIT { @@ -37,20 +44,18 @@ public class JarCommandIT { 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()); + assertThat(invocation.getStandardOutput(), equalTo("")); + assertThat(invocation.getErrorOutput(), containsString("The name of the " + + "resulting jar and at least one source file must be specified")); } @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()); + assertThat(invocation.getStandardOutput(), equalTo("")); + assertThat(invocation.getErrorOutput(), containsString("The name of the " + + "resulting jar and at least one source file must be specified")); } @Test @@ -62,14 +67,13 @@ public class JarCommandIT { 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(); + Process process = new JavaExecutable().processBuilder("-jar", + jar.getAbsolutePath()).start(); + invocation = new Invocation(process); + invocation.await(); - assertEquals(0, appInvocation.getErrorOutput().length()); - assertTrue(appInvocation.getStandardOutput().contains("Hello World!")); - assertTrue(appInvocation.getStandardOutput().contains("/static/test.txt")); + assertThat(invocation.getErrorOutput(), equalTo("")); + assertThat(invocation.getStandardOutput(), containsString("Hello World!")); + assertThat(invocation.getStandardOutput(), containsString("/static/test.txt")); } } 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 7a34aa88ea1..ad0f8aecb7e 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 @@ -18,20 +18,17 @@ package org.springframework.boot.cli.command.jar; import groovy.lang.Grab; -import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; +import java.io.InputStream; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.util.ArrayList; import java.util.Arrays; -import java.util.HashMap; import java.util.List; -import java.util.Map; -import java.util.Map.Entry; import java.util.jar.Manifest; 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.jar.ResourceMatcher.MatchedResource; 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.GroovyCompilerConfigurationAdapter; 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.Layout; 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 * * @author Andy Wilkinson + * @author Phillip Webb */ public class JarCommand extends OptionParsingCommand { @@ -77,9 +74,8 @@ public class JarCommand extends OptionParsingCommand { private static final Layout LAYOUT = new Layouts.Jar(); public JarCommand() { - super( - "jar", - "Create a self-contained executable jar file from a Spring Groovy script", + super("jar", "Create a self-contained " + + "executable jar file from a Spring Groovy script", new JarOptionHandler()); } @@ -110,156 +106,163 @@ public class JarCommand extends OptionParsingCommand { protected void run(OptionSet options) throws Exception { List nonOptionArguments = new ArrayList( options.nonOptionArguments()); - if (nonOptionArguments.size() < 2) { - throw new IllegalStateException( - "The name of the resulting jar and at least one source file must be specified"); - } + Assert.isTrue(nonOptionArguments.size() >= 2, + "The name of the resulting jar and at least one source file must be specified"); File output = new File((String) nonOptionArguments.remove(0)); - if (output.exists() && !output.delete()) { - throw new IllegalStateException( - "Failed to delete existing application jar file " - + output.getPath()); - } + deleteIfExists(output); - GroovyCompiler groovyCompiler = createCompiler(options); + GroovyCompiler compiler = createCompiler(options); - List classpathUrls = Arrays.asList(groovyCompiler.getLoader().getURLs()); - List classpathEntries = findClasspathEntries(classpathUrls, - options); + List classpath = getClassPathUrls(compiler); + List classpathEntries = findMatchingClasspathEntries( + classpath, options); - final Map compiledClasses = new HashMap(); - groovyCompiler.compile(new CompilationCallback() { + String[] sources = new SourceOptions(nonOptionArguments).getSourcesArray(); + Class[] compiledClasses = compiler.compile(sources); - @Override - public void byteCodeGenerated(byte[] byteCode, ClassNode classNode) - throws IOException { - String className = classNode.getName(); - compiledClasses.put(className, byteCode); - } + List dependencies = getClassPathUrls(compiler); + dependencies.removeAll(classpath); - }, new SourceOptions(nonOptionArguments).getSourcesArray()); + writeJar(output, compiledClasses, classpathEntries, dependencies); + } - List dependencyUrls = new ArrayList(Arrays.asList(groovyCompiler - .getLoader().getURLs())); - dependencyUrls.removeAll(classpathUrls); - - 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 void deleteIfExists(File file) { + if (file.exists() && !file.delete()) { + throw new IllegalStateException("Failed to delete existing file " + + file.getPath()); } } private GroovyCompiler createCompiler(OptionSet options) { List repositoryConfiguration = RepositoryConfigurationFactory .createDefaultRepositoryConfiguration(); - GroovyCompilerConfiguration configuration = new GroovyCompilerConfigurationAdapter( options, this, repositoryConfiguration); - GroovyCompiler groovyCompiler = new GroovyCompiler(configuration); - groovyCompiler.getAstTransformations().add(0, new ASTTransformation() { - - @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); - } - } - } - } - }); + groovyCompiler.getAstTransformations().add(0, new GrabAnnotationTransform()); return groovyCompiler; } - private List findClasspathEntries(List classpath, + private List getClassPathUrls(GroovyCompiler compiler) { + return new ArrayList(Arrays.asList(compiler.getLoader().getURLs())); + } + + private List findMatchingClasspathEntries(List classpath, OptionSet options) throws IOException { - ResourceMatcher resourceCollector = new ResourceMatcher( + ResourceMatcher matcher = new ResourceMatcher( options.valuesOf(this.includeOption), options.valuesOf(this.excludeOption)); - List roots = new ArrayList(); - for (URL classpathEntry : classpath) { roots.add(new File(URI.create(classpathEntry.toString()))); } - - return resourceCollector.matchResources(roots); + return matcher.find(roots); } - private Manifest createManifest(final Map compiledClasses) { + private void writeJar(File file, Class[] compiledClasses, + List classpathEntries, List 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.getMainAttributes().putValue("Manifest-Version", "1.0"); - manifest.getMainAttributes() - .putValue( - "Application-Classes", - StringUtils.collectionToCommaDelimitedString(compiledClasses - .keySet())); manifest.getMainAttributes().putValue("Main-Class", LAYOUT.getLauncherClassName()); manifest.getMainAttributes().putValue("Start-Class", PackagedSpringApplicationLauncher.class.getName()); - return manifest; + manifest.getMainAttributes().putValue( + PackagedSpringApplicationLauncher.SOURCE_MANIFEST_ENTRY, + commaDelimitedClassNames(compiledClasses)); + writer.writeManifest(manifest); } - private void addDependencies(JarWriter jarWriter, List urls) - throws IOException, URISyntaxException, FileNotFoundException { - for (URL url : urls) { - addDependency(jarWriter, new File(url.toURI())); + private String commaDelimitedClassNames(Class[] classes) { + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < classes.length; i++) { + builder.append(i == 0 ? "" : ","); + builder.append(classes[i].getName()); } + return builder.toString(); } - private void addDependency(JarWriter jarWriter, File dependency) - throws FileNotFoundException, IOException { - if (dependency.isFile()) { - jarWriter.writeNestedLibrary("lib/", dependency); - } + private void addCliClasses(JarWriter writer) throws IOException { + addClass(writer, PackagedSpringApplicationLauncher.class); } - private void addClasspathEntries(JarWriter jarWriter, - List classpathEntries) throws IOException { - for (MatchedResource classpathEntry : classpathEntries) { - if (classpathEntry.isRoot()) { - addDependency(jarWriter, classpathEntry.getFile()); + private void addClass(JarWriter writer, Class sourceClass) throws IOException { + String name = sourceClass.getName().replace(".", "/") + ".class"; + InputStream stream = sourceClass.getResourceAsStream("/" + name); + writer.writeEntry(name, stream); + } + + private void addClasspathEntries(JarWriter writer, List entries) + throws IOException { + for (MatchedResource entry : entries) { + if (entry.isRoot()) { + addDependency(writer, entry.getFile()); } else { - jarWriter.writeEntry(classpathEntry.getPath(), new FileInputStream( - classpathEntry.getFile())); + writer.writeEntry(entry.getName(), + new FileInputStream(entry.getFile())); } } } - private void addApplicationClasses(JarWriter jarWriter, - final Map compiledClasses) throws IOException { - - for (Entry entry : compiledClasses.entrySet()) { - jarWriter.writeEntry(getClassFile(entry.getKey()), - new ByteArrayInputStream(entry.getValue())); + private void addDependencies(JarWriter writer, List urls) + throws IOException, URISyntaxException, FileNotFoundException { + for (URL url : urls) { + addDependency(writer, new File(url.toURI())); } } - private String getClassFile(String className) { - return className.replace(".", "/") + ".class"; + private void addDependency(JarWriter writer, File dependency) + 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); + } + } + + } + } 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 7c4754a4aef..7523b11b40d 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 @@ -28,6 +28,7 @@ import java.util.List; import org.springframework.core.io.DefaultResourceLoader; import org.springframework.core.io.FileSystemResource; import org.springframework.core.io.Resource; +import org.springframework.core.io.ResourceLoader; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; import org.springframework.util.AntPathMatcher; @@ -36,7 +37,7 @@ import org.springframework.util.AntPathMatcher; * * @author Andy Wilkinson */ -final class ResourceMatcher { +class ResourceMatcher { private final AntPathMatcher pathMatcher = new AntPathMatcher(); @@ -49,110 +50,128 @@ final class ResourceMatcher { this.excludes = excludes; } - List matchResources(List roots) throws IOException { + public List find(List roots) throws IOException { List matchedResources = new ArrayList(); - for (File root : roots) { if (root.isFile()) { matchedResources.add(new MatchedResource(root)); } else { - matchedResources.addAll(matchResources(root)); + matchedResources.addAll(findInFolder(root)); } } return matchedResources; } - private List matchResources(File root) throws IOException { - List resources = new ArrayList(); + private List findInFolder(File folder) throws IOException { + List matchedResources = new ArrayList(); PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver( - new ResourceCollectionResourceLoader(root)); + new FolderResourceLoader(folder)); for (String include : this.includes) { - Resource[] candidates = resolver.getResources(include); - for (Resource candidate : candidates) { + for (Resource candidate : resolver.getResources(include)) { File file = candidate.getFile(); if (file.isFile()) { - MatchedResource matchedResource = new MatchedResource(root, file); + MatchedResource matchedResource = new MatchedResource(folder, file); if (!isExcluded(matchedResource)) { - resources.add(matchedResource); + matchedResources.add(matchedResource); } } } } - return resources; + return matchedResources; } private boolean isExcluded(MatchedResource matchedResource) { for (String exclude : this.excludes) { - if (this.pathMatcher.match(exclude, matchedResource.getPath())) { + if (this.pathMatcher.match(exclude, matchedResource.getName())) { return true; } } - 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 { - super(new URLClassLoader(new URL[] { root.toURI().toURL() }) { - @Override - public Enumeration getResources(String name) throws IOException { - return findResources(name); - } - - @Override - public URL getResource(String name) { - return findResource(name); - } - }); - this.root = root; + public FolderResourceLoader(File root) throws MalformedURLException { + super(new FolderClassLoader(root)); + this.rootFolder = root; } @Override 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 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 String path; + private final String name; private final boolean root; - private MatchedResource(File resourceFile) { - this(resourceFile, resourceFile.getName(), true); + private MatchedResource(File file) { + this.name = file.getName(); + this.file = file; + this.root = false; } - private MatchedResource(File root, File resourceFile) { - this(resourceFile, resourceFile.getAbsolutePath().substring( - root.getAbsolutePath().length() + 1), false); + private MatchedResource(File rootFolder, File file) { + this.name = file.getAbsolutePath().substring( + rootFolder.getAbsolutePath().length() + 1); + this.file = file; + this.root = false; } private MatchedResource(File resourceFile, String path, boolean root) { this.file = resourceFile; - this.path = path; + this.name = path; this.root = root; } - File getFile() { + public String getName() { + return this.name; + } + + public File getFile() { return this.file; } - String getPath() { - return this.path; - } - - boolean isRoot() { + public boolean isRoot() { return this.root; } @@ -160,6 +179,7 @@ final class ResourceMatcher { public String toString() { return this.file.getAbsolutePath(); } + } } diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/shell/ForkProcessCommand.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/shell/ForkProcessCommand.java index 414f3b64bd6..d50ccd59fe0 100644 --- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/shell/ForkProcessCommand.java +++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/shell/ForkProcessCommand.java @@ -16,8 +16,6 @@ package org.springframework.boot.cli.command.shell; -import java.io.File; -import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; 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.OptionHelp; -import org.springframework.util.Assert; -import org.springframework.util.StringUtils; +import org.springframework.boot.cli.util.JavaExecutable; /** * 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; public ForkProcessCommand(Command command) { - super(getJavaCommand()); + super(new JavaExecutable().toString()); this.command = command; } @@ -80,24 +77,4 @@ class ForkProcessCommand extends RunProcessCommand { 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; - } - } diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/GroovyCompiler.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/GroovyCompiler.java index b0998ab6cc2..368f2fb9c35 100644 --- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/GroovyCompiler.java +++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/GroovyCompiler.java @@ -34,7 +34,6 @@ import org.codehaus.groovy.ast.ClassNode; import org.codehaus.groovy.classgen.GeneratorContext; import org.codehaus.groovy.control.CompilationFailedException; import org.codehaus.groovy.control.CompilationUnit; -import org.codehaus.groovy.control.CompilationUnit.ClassgenCallback; import org.codehaus.groovy.control.CompilePhase; import org.codehaus.groovy.control.CompilerConfiguration; 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.transform.ASTTransformation; 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.AetherGrapeEngineFactory; 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 getAstTransformations() { return this.transformations; } @@ -210,42 +211,6 @@ public class GroovyCompiler { 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 paths = ResourceUtils.getUrls(source, this.loader); - for (String path : paths) { - compilationUnit.addSource(new URL(path)); - } - } - - addAstTransformations(compilationUnit); - - compilationUnit.compile(Phases.CLASS_GENERATION); - } - @SuppressWarnings("rawtypes") private void addAstTransformations(CompilationUnit 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; - } - } diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/jar/PackagedSpringApplicationLauncher.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/jar/PackagedSpringApplicationLauncher.java index f59086a2f9c..78f82a7acf9 100644 --- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/jar/PackagedSpringApplicationLauncher.java +++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/jar/PackagedSpringApplicationLauncher.java @@ -29,6 +29,8 @@ import java.util.jar.Manifest; */ 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 void run(String[] args) throws Exception { @@ -42,7 +44,7 @@ public class PackagedSpringApplicationLauncher { private Object[] getSources(URLClassLoader classLoader) throws Exception { URL url = classLoader.findResource("META-INF/MANIFEST.MF"); 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(",")); } diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/util/JavaExecutable.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/util/JavaExecutable.java new file mode 100644 index 00000000000..d03d8e47254 --- /dev/null +++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/util/JavaExecutable.java @@ -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); + } + } + +} 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 23431d1e094..288e48a594a 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 @@ -29,7 +29,9 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; /** - * @author awilkinson + * Tests for {@link ResourceMatcher}. + * + * @author Andy Wilkinson */ public class ResourceMatcherTests { @@ -38,22 +40,21 @@ public class ResourceMatcherTests { @Test public void nonExistentRoot() throws IOException { - List matchedResources = this.resourceMatcher - .matchResources(Arrays.asList(new File("does-not-exist"))); + List matchedResources = this.resourceMatcher.find(Arrays + .asList(new File("does-not-exist"))); assertEquals(0, matchedResources.size()); } @Test public void resourceMatching() throws IOException { - List matchedResources = this.resourceMatcher - .matchResources(Arrays.asList(new File( - "src/test/resources/resource-matcher/one"), new File( + List matchedResources = this.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"))); System.out.println(matchedResources); List paths = new ArrayList(); for (MatchedResource resource : matchedResources) { - paths.add(resource.getPath()); + paths.add(resource.getName()); } assertEquals(6, paths.size());