[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");

View File

@ -20,11 +20,18 @@ import javax.sql.DataSource;
import org.springframework.batch.support.DatabaseType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.ResourceLoader;
import org.springframework.jdbc.datasource.init.DatabasePopulatorUtils;
import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;
import org.springframework.stereotype.Component;
/**
* Initialize the Spring Batch schema (ignoring errors, so should be idempotent).
*
* @author Dave Syer
*
*/
@Component
public class BatchDatabaseInitializer {
@ -34,6 +41,9 @@ public class BatchDatabaseInitializer {
@Autowired
private ResourceLoader resourceLoader;
@Value("${spring.batch.schema:classpath:org/springframework/batch/core/schema-@@platform@@.sql}")
private String schemaLocation = "classpath:org/springframework/batch/core/schema-@@platform@@.sql";
@PostConstruct
protected void initialize() throws Exception {
String platform = DatabaseType.fromMetaData(this.dataSource).toString()
@ -42,12 +52,9 @@ public class BatchDatabaseInitializer {
platform = "hsqldb";
}
ResourceDatabasePopulator populator = new ResourceDatabasePopulator();
populator
.addScript(this.resourceLoader
.getResource("org/springframework/batch/core/schema-" + platform
+ ".sql"));
populator.addScript(this.resourceLoader.getResource(this.schemaLocation.replace(
"@@platform@@", platform)));
populator.setContinueOnError(true);
DatabasePopulatorUtils.execute(populator, this.dataSource);
}
}

View File

@ -16,6 +16,7 @@
package org.springframework.bootstrap.context.annotation;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@ -47,8 +48,17 @@ public abstract class AutoConfigurationUtils {
public static void storeBasePackages(ConfigurableListableBeanFactory beanFactory,
List<String> basePackages) {
beanFactory.registerSingleton(BASE_PACKAGES_BEAN,
Collections.unmodifiableList(basePackages));
if (!beanFactory.containsBean(BASE_PACKAGES_BEAN)) {
beanFactory.registerSingleton(BASE_PACKAGES_BEAN, new ArrayList<String>(
basePackages));
} else {
List<String> packages = getBasePackages(beanFactory);
for (String pkg : basePackages) {
if (packages.contains(pkg)) {
packages.add(pkg);
}
}
}
}
}