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:
@@ -22,6 +22,7 @@ import java.util.List;
|
||||
|
||||
import org.springframework.boot.cli.command.core.VersionCommand;
|
||||
import org.springframework.boot.cli.command.grab.GrabCommand;
|
||||
import org.springframework.boot.cli.command.jar.JarCommand;
|
||||
import org.springframework.boot.cli.command.run.RunCommand;
|
||||
import org.springframework.boot.cli.command.test.TestCommand;
|
||||
|
||||
@@ -33,7 +34,8 @@ import org.springframework.boot.cli.command.test.TestCommand;
|
||||
public class DefaultCommandFactory implements CommandFactory {
|
||||
|
||||
private static final List<Command> DEFAULT_COMMANDS = Arrays.<Command> asList(
|
||||
new VersionCommand(), new RunCommand(), new TestCommand(), new GrabCommand());
|
||||
new VersionCommand(), new RunCommand(), new TestCommand(), new GrabCommand(),
|
||||
new JarCommand());
|
||||
|
||||
@Override
|
||||
public Collection<Command> getCommands() {
|
||||
|
||||
@@ -31,6 +31,7 @@ import org.springframework.util.Assert;
|
||||
* @author Phillip Webb
|
||||
* @author Dave Syer
|
||||
* @author Greg Turnquist
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
public class SourceOptions {
|
||||
|
||||
@@ -46,6 +47,14 @@ public class SourceOptions {
|
||||
this(options, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link SourceOptions} instance.
|
||||
* @param arguments the source arguments
|
||||
*/
|
||||
public SourceOptions(List<?> arguments) {
|
||||
this(arguments, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link SourceOptions} instance. If it is an error to pass options that
|
||||
* specify non-existent sources, but the default paths are allowed not to exist (the
|
||||
@@ -57,7 +66,10 @@ public class SourceOptions {
|
||||
* found in the local filesystem
|
||||
*/
|
||||
public SourceOptions(OptionSet optionSet, ClassLoader classLoader) {
|
||||
List<?> nonOptionArguments = optionSet.nonOptionArguments();
|
||||
this(optionSet.nonOptionArguments(), classLoader);
|
||||
}
|
||||
|
||||
private SourceOptions(List<?> nonOptionArguments, ClassLoader classLoader) {
|
||||
List<String> sources = new ArrayList<String>();
|
||||
int sourceArgCount = 0;
|
||||
for (Object option : nonOptionArguments) {
|
||||
@@ -84,7 +96,7 @@ public class SourceOptions {
|
||||
}
|
||||
this.args = Collections.unmodifiableList(nonOptionArguments.subList(
|
||||
sourceArgCount, nonOptionArguments.size()));
|
||||
Assert.isTrue(sources.size() > 0, "Please specify at least one file to run");
|
||||
Assert.isTrue(sources.size() > 0, "Please specify at least one file");
|
||||
this.sources = Collections.unmodifiableList(sources);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,269 @@
|
||||
/*
|
||||
* 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.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.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarFile;
|
||||
import java.util.jar.Manifest;
|
||||
|
||||
import joptsimple.OptionSet;
|
||||
import joptsimple.OptionSpec;
|
||||
|
||||
import org.codehaus.groovy.ast.ASTNode;
|
||||
import org.codehaus.groovy.ast.AnnotationNode;
|
||||
import org.codehaus.groovy.ast.ClassNode;
|
||||
import org.codehaus.groovy.ast.ModuleNode;
|
||||
import org.codehaus.groovy.ast.expr.ConstantExpression;
|
||||
import org.codehaus.groovy.control.SourceUnit;
|
||||
import org.codehaus.groovy.transform.ASTTransformation;
|
||||
import org.springframework.boot.cli.command.Command;
|
||||
import org.springframework.boot.cli.command.CompilerOptionHandler;
|
||||
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;
|
||||
import org.springframework.boot.cli.compiler.grape.RepositoryConfiguration;
|
||||
import org.springframework.boot.loader.ArchiveResolver;
|
||||
import org.springframework.boot.loader.tools.JarWriter;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* {@link Command} to create a self-contained executable jar file from a CLI application
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
public class JarCommand extends OptionParsingCommand {
|
||||
|
||||
public JarCommand() {
|
||||
super(
|
||||
"jar",
|
||||
"Create a self-contained executable jar file from a Spring Groovy script",
|
||||
new JarOptionHandler());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsageHelp() {
|
||||
return "[options] <jar-name> <files>";
|
||||
}
|
||||
|
||||
private static final class JarOptionHandler extends CompilerOptionHandler {
|
||||
|
||||
private OptionSpec<String> includeOption;
|
||||
|
||||
private OptionSpec<String> excludeOption;
|
||||
|
||||
@Override
|
||||
protected void doOptions() {
|
||||
this.includeOption = option(
|
||||
"include",
|
||||
"Pattern applied to directories on the classpath to find files to include in the resulting jar")
|
||||
.withRequiredArg().defaultsTo("public/**", "static/**",
|
||||
"resources/**", "META-INF/**", "*");
|
||||
this.excludeOption = option(
|
||||
"exclude",
|
||||
"Pattern applied to directories on the claspath to find files to exclude from the resulting jar")
|
||||
.withRequiredArg().defaultsTo(".*", "repository/**", "build/**",
|
||||
"target/**");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void run(OptionSet options) throws Exception {
|
||||
List<?> nonOptionArguments = new ArrayList<Object>(
|
||||
options.nonOptionArguments());
|
||||
if (nonOptionArguments.size() < 2) {
|
||||
throw new IllegalStateException(
|
||||
"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());
|
||||
}
|
||||
|
||||
GroovyCompiler groovyCompiler = createCompiler(options);
|
||||
|
||||
List<URL> classpathUrls = Arrays.asList(groovyCompiler.getLoader().getURLs());
|
||||
List<MatchedResource> classpathEntries = findClasspathEntries(classpathUrls,
|
||||
options);
|
||||
|
||||
final Map<String, byte[]> compiledClasses = new HashMap<String, byte[]>();
|
||||
groovyCompiler.compile(new CompilationCallback() {
|
||||
|
||||
@Override
|
||||
public void byteCodeGenerated(byte[] byteCode, ClassNode classNode)
|
||||
throws IOException {
|
||||
String className = classNode.getName();
|
||||
compiledClasses.put(className, byteCode);
|
||||
}
|
||||
|
||||
}, new SourceOptions(nonOptionArguments).getSourcesArray());
|
||||
|
||||
List<URL> dependencyUrls = new ArrayList<URL>(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);
|
||||
jarWriter.writeLoaderClasses();
|
||||
addJarRunner(jarWriter);
|
||||
}
|
||||
finally {
|
||||
jarWriter.close();
|
||||
}
|
||||
}
|
||||
|
||||
private GroovyCompiler createCompiler(OptionSet options) {
|
||||
List<RepositoryConfiguration> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
return groovyCompiler;
|
||||
}
|
||||
|
||||
private List<MatchedResource> findClasspathEntries(List<URL> classpath,
|
||||
OptionSet options) throws IOException {
|
||||
ResourceMatcher resourceCollector = new ResourceMatcher(
|
||||
options.valuesOf(this.includeOption),
|
||||
options.valuesOf(this.excludeOption));
|
||||
|
||||
List<File> roots = new ArrayList<File>();
|
||||
|
||||
for (URL classpathEntry : classpath) {
|
||||
roots.add(new File(URI.create(classpathEntry.toString())));
|
||||
}
|
||||
|
||||
return resourceCollector.matchResources(roots);
|
||||
}
|
||||
|
||||
private Manifest createManifest(final Map<String, byte[]> compiledClasses) {
|
||||
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", JarRunner.class.getName());
|
||||
return manifest;
|
||||
}
|
||||
|
||||
private void addDependencies(JarWriter jarWriter, List<URL> urls)
|
||||
throws IOException, URISyntaxException, FileNotFoundException {
|
||||
for (URL url : urls) {
|
||||
addDependency(jarWriter, new File(url.toURI()));
|
||||
}
|
||||
}
|
||||
|
||||
private void addDependency(JarWriter jarWriter, File dependency)
|
||||
throws FileNotFoundException, IOException {
|
||||
if (dependency.isFile()) {
|
||||
jarWriter.writeNestedLibrary("lib/", dependency);
|
||||
}
|
||||
}
|
||||
|
||||
private void addClasspathEntries(JarWriter jarWriter,
|
||||
List<MatchedResource> classpathEntries) throws IOException {
|
||||
for (MatchedResource classpathEntry : classpathEntries) {
|
||||
if (classpathEntry.isRoot()) {
|
||||
addDependency(jarWriter, classpathEntry.getFile());
|
||||
}
|
||||
else {
|
||||
jarWriter.writeEntry(classpathEntry.getPath(), new FileInputStream(
|
||||
classpathEntry.getFile()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void addApplicationClasses(JarWriter jarWriter,
|
||||
final Map<String, byte[]> compiledClasses) throws IOException {
|
||||
|
||||
for (Entry<String, byte[]> entry : compiledClasses.entrySet()) {
|
||||
String className = entry.getKey().replace(".", "/") + ".class";
|
||||
jarWriter.writeEntry(className,
|
||||
new ByteArrayInputStream(entry.getValue()));
|
||||
}
|
||||
}
|
||||
|
||||
private void addJarRunner(JarWriter jar) throws IOException, URISyntaxException {
|
||||
JarFile jarFile = getJarFile();
|
||||
String namePrefix = JarRunner.class.getName().replace(".", "/");
|
||||
Enumeration<JarEntry> entries = jarFile.entries();
|
||||
while (entries.hasMoreElements()) {
|
||||
JarEntry entry = entries.nextElement();
|
||||
if (entry.getName().startsWith(namePrefix)) {
|
||||
jar.writeEntry(entry.getName(), jarFile.getInputStream(entry));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private JarFile getJarFile() throws URISyntaxException, IOException {
|
||||
return new JarFile(
|
||||
new ArchiveResolver().resolveArchiveLocation(JarCommand.class));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* 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.command.jar;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.boot.loader.ArchiveResolver;
|
||||
import org.springframework.boot.loader.AsciiBytes;
|
||||
import org.springframework.boot.loader.LaunchedURLClassLoader;
|
||||
import org.springframework.boot.loader.archive.Archive;
|
||||
import org.springframework.boot.loader.archive.Archive.Entry;
|
||||
import org.springframework.boot.loader.archive.Archive.EntryFilter;
|
||||
|
||||
/**
|
||||
* A runner for a CLI application that has been compiled and packaged as a jar file
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
public class JarRunner {
|
||||
|
||||
private static final AsciiBytes LIB = new AsciiBytes("lib/");
|
||||
|
||||
public static void main(String[] args) throws URISyntaxException, IOException,
|
||||
ClassNotFoundException, SecurityException, NoSuchMethodException,
|
||||
IllegalArgumentException, IllegalAccessException, InvocationTargetException {
|
||||
|
||||
Archive archive = new ArchiveResolver().resolveArchive(JarRunner.class);
|
||||
|
||||
ClassLoader classLoader = createClassLoader(archive);
|
||||
Class<?>[] classes = loadApplicationClasses(archive, classLoader);
|
||||
|
||||
Thread.currentThread().setContextClassLoader(classLoader);
|
||||
|
||||
// Use reflection to load and call Spring
|
||||
Class<?> application = classLoader
|
||||
.loadClass("org.springframework.boot.SpringApplication");
|
||||
Method method = application.getMethod("run", Object[].class, String[].class);
|
||||
method.invoke(null, classes, args);
|
||||
|
||||
}
|
||||
|
||||
private static ClassLoader createClassLoader(Archive archive) throws IOException,
|
||||
MalformedURLException {
|
||||
List<Archive> nestedArchives = archive.getNestedArchives(new EntryFilter() {
|
||||
|
||||
@Override
|
||||
public boolean matches(Entry entry) {
|
||||
return entry.getName().startsWith(LIB);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
List<URL> urls = new ArrayList<URL>();
|
||||
urls.add(archive.getUrl());
|
||||
for (Archive nestedArchive : nestedArchives) {
|
||||
urls.add(nestedArchive.getUrl());
|
||||
}
|
||||
|
||||
ClassLoader classLoader = new LaunchedURLClassLoader(urls.toArray(new URL[urls
|
||||
.size()]), JarRunner.class.getClassLoader());
|
||||
return classLoader;
|
||||
}
|
||||
|
||||
private static Class<?>[] loadApplicationClasses(Archive archive,
|
||||
ClassLoader classLoader) throws ClassNotFoundException, IOException {
|
||||
String[] classNames = archive.getManifest().getMainAttributes()
|
||||
.getValue("Application-Classes").split(",");
|
||||
|
||||
Class<?>[] classes = new Class<?>[classNames.length];
|
||||
|
||||
for (int i = 0; i < classNames.length; i++) {
|
||||
Class<?> applicationClass = classLoader.loadClass(classNames[i]);
|
||||
classes[i] = applicationClass;
|
||||
}
|
||||
return classes;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
/*
|
||||
* 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.command.jar;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Enumeration;
|
||||
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.support.PathMatchingResourcePatternResolver;
|
||||
import org.springframework.util.AntPathMatcher;
|
||||
|
||||
/**
|
||||
* Used to match resources for inclusion in a CLI application's jar file
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
final class ResourceMatcher {
|
||||
|
||||
private final AntPathMatcher pathMatcher = new AntPathMatcher();
|
||||
|
||||
private final List<String> includes;
|
||||
|
||||
private final List<String> excludes;
|
||||
|
||||
ResourceMatcher(List<String> includes, List<String> excludes) {
|
||||
this.includes = includes;
|
||||
this.excludes = excludes;
|
||||
}
|
||||
|
||||
List<MatchedResource> matchResources(List<File> roots) throws IOException {
|
||||
List<MatchedResource> matchedResources = new ArrayList<MatchedResource>();
|
||||
|
||||
for (File root : roots) {
|
||||
if (root.isFile()) {
|
||||
matchedResources.add(new MatchedResource(root));
|
||||
}
|
||||
else {
|
||||
matchedResources.addAll(matchResources(root));
|
||||
}
|
||||
}
|
||||
return matchedResources;
|
||||
}
|
||||
|
||||
private List<MatchedResource> matchResources(File root) throws IOException {
|
||||
List<MatchedResource> resources = new ArrayList<MatchedResource>();
|
||||
|
||||
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(
|
||||
new ResourceCollectionResourceLoader(root));
|
||||
|
||||
for (String include : this.includes) {
|
||||
Resource[] candidates = resolver.getResources(include);
|
||||
for (Resource candidate : candidates) {
|
||||
File file = candidate.getFile();
|
||||
if (file.isFile()) {
|
||||
MatchedResource matchedResource = new MatchedResource(root, file);
|
||||
if (!isExcluded(matchedResource)) {
|
||||
resources.add(matchedResource);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return resources;
|
||||
}
|
||||
|
||||
private boolean isExcluded(MatchedResource matchedResource) {
|
||||
for (String exclude : this.excludes) {
|
||||
if (this.pathMatcher.match(exclude, matchedResource.getPath())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static final class ResourceCollectionResourceLoader extends
|
||||
DefaultResourceLoader {
|
||||
|
||||
private final File root;
|
||||
|
||||
ResourceCollectionResourceLoader(File root) throws MalformedURLException {
|
||||
super(new URLClassLoader(new URL[] { root.toURI().toURL() }) {
|
||||
@Override
|
||||
public Enumeration<URL> getResources(String name) throws IOException {
|
||||
return findResources(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public URL getResource(String name) {
|
||||
return findResource(name);
|
||||
}
|
||||
});
|
||||
this.root = root;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Resource getResourceByPath(String path) {
|
||||
return new FileSystemResource(new File(this.root, path));
|
||||
}
|
||||
}
|
||||
|
||||
static final class MatchedResource {
|
||||
|
||||
private final File file;
|
||||
|
||||
private final String path;
|
||||
|
||||
private final boolean root;
|
||||
|
||||
private MatchedResource(File resourceFile) {
|
||||
this(resourceFile, resourceFile.getName(), true);
|
||||
}
|
||||
|
||||
private MatchedResource(File root, File resourceFile) {
|
||||
this(resourceFile, resourceFile.getAbsolutePath().substring(
|
||||
root.getAbsolutePath().length() + 1), false);
|
||||
}
|
||||
|
||||
private MatchedResource(File resourceFile, String path, boolean root) {
|
||||
this.file = resourceFile;
|
||||
this.path = path;
|
||||
this.root = root;
|
||||
}
|
||||
|
||||
File getFile() {
|
||||
return this.file;
|
||||
}
|
||||
|
||||
String getPath() {
|
||||
return this.path;
|
||||
}
|
||||
|
||||
boolean isRoot() {
|
||||
return this.root;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.file.getAbsolutePath();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -34,6 +34,7 @@ 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;
|
||||
@@ -42,6 +43,8 @@ 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;
|
||||
@@ -117,6 +120,10 @@ public class GroovyCompiler {
|
||||
}
|
||||
}
|
||||
|
||||
public List<ASTTransformation> getAstTransformations() {
|
||||
return this.transformations;
|
||||
}
|
||||
|
||||
public ExtendedGroovyClassLoader getLoader() {
|
||||
return this.loader;
|
||||
}
|
||||
@@ -203,6 +210,42 @@ 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<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")
|
||||
private void addAstTransformations(CompilationUnit compilationUnit) {
|
||||
LinkedList[] phaseOperations = getPhaseOperations(compilationUnit);
|
||||
@@ -286,4 +329,10 @@ public class GroovyCompiler {
|
||||
|
||||
}
|
||||
|
||||
public static interface CompilationCallback {
|
||||
|
||||
public void byteCodeGenerated(byte[] byteCode, ClassNode classNode)
|
||||
throws IOException;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user