[bs-24] Support multi-file compile from command line

* Each non-option arg is tested to see if it is a file
* If it is .groovy (or .java) it is compiled

[#48127661]
This commit is contained in:
Dave Syer
2013-04-29 15:18:45 +01:00
parent a5f7b6ead3
commit bb62ca835e
7 changed files with 118 additions and 45 deletions

View File

@@ -18,6 +18,7 @@ package org.springframework.bootstrap.cli;
import java.awt.Desktop;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
@@ -31,9 +32,11 @@ import org.springframework.bootstrap.cli.runner.BootstrapRunnerConfiguration;
import static java.util.Arrays.asList;
/**
* {@link Command} to 'run' a spring groovy script.
* {@link Command} to 'run' a groovy script or scripts.
*
* @author Phillip Webb
* @author Dave Syer
*
* @see BootstrapRunner
*/
public class RunCommand extends OptionParsingCommand {
@@ -58,7 +61,7 @@ public class RunCommand extends OptionParsingCommand {
@Override
public String getUsageHelp() {
return "[options] <file>";
return "[options] <files> [--] [args]";
}
public void stop() {
@@ -86,30 +89,40 @@ public class RunCommand extends OptionParsingCommand {
@Override
protected void run(OptionSet options) throws Exception {
List<String> nonOptionArguments = options.nonOptionArguments();
File file = getFileArgument(nonOptionArguments);
List<String> args = nonOptionArguments.subList(1, nonOptionArguments.size());
File[] files = getFileArguments(nonOptionArguments);
List<String> args = nonOptionArguments.subList(files.length,
nonOptionArguments.size());
if (options.has(this.editOption)) {
Desktop.getDesktop().edit(file);
Desktop.getDesktop().edit(files[0]);
}
BootstrapRunnerConfiguration configuration = new BootstrapRunnerConfigurationAdapter(
options);
this.runner = new BootstrapRunner(configuration, file,
this.runner = new BootstrapRunner(configuration, files,
args.toArray(new String[args.size()]));
this.runner.compileAndRun();
}
private File getFileArgument(List<String> nonOptionArguments) {
if (nonOptionArguments.size() == 0) {
private File[] getFileArguments(List<String> nonOptionArguments) {
List<File> files = new ArrayList<File>();
for (String filename : nonOptionArguments) {
if ("--".equals(filename)) {
break;
}
// TODO: add support for strict Java compilation
// TODO: add support for recursive search in directory
if (filename.endsWith(".groovy") || filename.endsWith(".java")) {
File file = new File(filename);
if (file.isFile() && file.canRead()) {
files.add(file);
}
}
}
if (files.size() == 0) {
throw new RuntimeException("Please specify a file to run");
}
String filename = nonOptionArguments.get(0);
File file = new File(filename);
if (!file.isFile() || !file.canRead()) {
throw new RuntimeException("Unable to read '" + filename + "'");
}
return file;
return files.toArray(new File[files.size()]);
}
/**

View File

@@ -35,13 +35,16 @@ import org.codehaus.groovy.control.SourceUnit;
* resources.
*
* @author Phillip Webb
* @author Dave Syer
*/
class ExtendedGroovyClassLoader extends GroovyClassLoader {
private Map<String, byte[]> classResources = new HashMap<String, byte[]>();
private CompilerConfiguration configuration;
public ExtendedGroovyClassLoader(ClassLoader loader, CompilerConfiguration config) {
super(loader, config);
this.configuration = config;
}
@Override
@@ -54,8 +57,12 @@ class ExtendedGroovyClassLoader extends GroovyClassLoader {
return resourceStream;
}
public CompilerConfiguration getConfiguration() {
return this.configuration;
}
@Override
protected ClassCollector createCollector(CompilationUnit unit, SourceUnit su) {
public ClassCollector createCollector(CompilationUnit unit, SourceUnit su) {
InnerLoader loader = AccessController
.doPrivileged(new PrivilegedAction<InnerLoader>() {
@Override

View File

@@ -17,6 +17,7 @@
package org.springframework.bootstrap.cli.compiler;
import groovy.lang.GroovyClassLoader;
import groovy.lang.GroovyClassLoader.ClassCollector;
import java.io.File;
import java.io.IOException;
@@ -26,8 +27,10 @@ import java.util.List;
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.CompilePhase;
import org.codehaus.groovy.control.CompilerConfiguration;
import org.codehaus.groovy.control.Phases;
import org.codehaus.groovy.control.SourceUnit;
import org.codehaus.groovy.control.customizers.CompilationCustomizer;
import org.codehaus.groovy.control.customizers.ImportCustomizer;
@@ -49,6 +52,7 @@ import org.springframework.bootstrap.cli.compiler.autoconfigure.SpringMvcCompile
* <ul>
*
* @author Phillip Webb
* @author Dave Syer
*/
public class GroovyCompiler {
@@ -79,24 +83,37 @@ public class GroovyCompiler {
/**
* Compile the specified Groovy source files, applying any
* {@link CompilerAutoConfiguration}s. All classes defined in the file will be
* returned from this method with the first item being the primary class (defined at
* the top of the file).
* {@link CompilerAutoConfiguration}s. All classes defined in the files will be
* returned from this method.
* @param file the file to compile
* @return compiled classes
* @throws CompilationFailedException
* @throws IOException
*/
public Class<?>[] compile(File file) throws CompilationFailedException, IOException {
public Class<?>[] compile(File... file) throws CompilationFailedException,
IOException {
this.loader.clearCache();
List<Class<?>> classes = new ArrayList<Class<?>>();
Class<?> mainClass = this.loader.parseClass(file);
for (Class<?> loadedClass : this.loader.getLoadedClasses()) {
classes.add(loadedClass);
CompilerConfiguration compilerConfiguration = this.loader.getConfiguration();
CompilationUnit compilationUnit = new CompilationUnit(compilerConfiguration,
null, this.loader);
SourceUnit sourceUnit = new SourceUnit(file[0], compilerConfiguration,
this.loader, compilationUnit.getErrorCollector());
ClassCollector collector = this.loader.createCollector(compilationUnit,
sourceUnit);
compilationUnit.setClassgenCallback(collector);
compilationUnit.addSources(file);
compilationUnit.compile(Phases.CLASS_GENERATION);
for (Object loadedClass : collector.getLoadedClasses()) {
classes.add((Class<?>) loadedClass);
}
classes.remove(mainClass);
classes.add(0, mainClass);
return classes.toArray(new Class<?>[classes.size()]);
}
/**

View File

@@ -37,7 +37,7 @@ public class BootstrapRunner {
private BootstrapRunnerConfiguration configuration;
private final File file;
private final File[] files;
private final String[] args;
@@ -50,13 +50,13 @@ public class BootstrapRunner {
/**
* Create a new {@link BootstrapRunner} instance.
* @param configuration the configuration
* @param file the file to compile/watch
* @param files the files to compile/watch
* @param args input arguments
*/
public BootstrapRunner(final BootstrapRunnerConfiguration configuration, File file,
String... args) {
public BootstrapRunner(final BootstrapRunnerConfiguration configuration,
File[] files, String... args) {
this.configuration = configuration;
this.file = file;
this.files = files;
this.args = args;
this.compiler = new GroovyCompiler(configuration);
if (configuration.getLogLevel().intValue() <= Level.FINE.intValue()) {
@@ -75,9 +75,9 @@ public class BootstrapRunner {
stop();
// Compile
Class<?>[] classes = this.compiler.compile(this.file);
Class<?>[] classes = this.compiler.compile(this.files);
if (classes.length == 0) {
throw new RuntimeException("No classes found in '" + this.file + "'");
throw new RuntimeException("No classes found in '" + this.files + "'");
}
// Run in new thread to ensure that the context classloader is setup
@@ -164,7 +164,13 @@ public class BootstrapRunner {
private long previous;
public FileWatchThread() {
this.previous = BootstrapRunner.this.file.lastModified();
this.previous = 0;
for (File file : BootstrapRunner.this.files) {
long current = file.lastModified();
if (current > this.previous) {
this.previous = current;
}
}
setDaemon(false);
}
@@ -173,10 +179,12 @@ public class BootstrapRunner {
while (true) {
try {
Thread.sleep(TimeUnit.SECONDS.toMillis(1));
long current = BootstrapRunner.this.file.lastModified();
if (this.previous < current) {
this.previous = current;
compileAndRun();
for (File file : BootstrapRunner.this.files) {
long current = file.lastModified();
if (this.previous < current) {
this.previous = current;
compileAndRun();
}
}
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();

View File

@@ -59,7 +59,7 @@ public class SampleIntegrationTests {
return this.output.toString();
}
private void start(final String sample) throws Exception {
private void start(final String... sample) throws Exception {
Future<RunCommand> future = Executors.newSingleThreadExecutor().submit(
new Callable<RunCommand>() {
@Override
@@ -94,12 +94,23 @@ public class SampleIntegrationTests {
@Test
public void jobSample() throws Exception {
start("samples/job.groovy");
start("samples/job.groovy", "foo=bar");
String output = getOutput();
assertTrue("Wrong output: " + output,
output.contains("completed with the following parameters"));
}
@Test
public void jobWebSample() throws Exception {
start("samples/job.groovy", "samples/web.groovy", "foo=bar");
String output = getOutput();
assertTrue("Wrong output: " + output,
output.contains("completed with the following parameters"));
String result = FileUtil.readEntirely(new URL("http://localhost:8080")
.openStream());
assertEquals("World!", result);
}
@Test
public void webSample() throws Exception {
start("samples/web.groovy");