mirror of
https://github.com/spring-projects/spring-boot.git
synced 2024-07-05 00:56:58 +08:00
[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:
parent
a5f7b6ead3
commit
bb62ca835e
@ -18,6 +18,7 @@ package org.springframework.bootstrap.cli;
|
|||||||
|
|
||||||
import java.awt.Desktop;
|
import java.awt.Desktop;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
|
|
||||||
@ -31,9 +32,11 @@ import org.springframework.bootstrap.cli.runner.BootstrapRunnerConfiguration;
|
|||||||
import static java.util.Arrays.asList;
|
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 Phillip Webb
|
||||||
|
* @author Dave Syer
|
||||||
|
*
|
||||||
* @see BootstrapRunner
|
* @see BootstrapRunner
|
||||||
*/
|
*/
|
||||||
public class RunCommand extends OptionParsingCommand {
|
public class RunCommand extends OptionParsingCommand {
|
||||||
@ -58,7 +61,7 @@ public class RunCommand extends OptionParsingCommand {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getUsageHelp() {
|
public String getUsageHelp() {
|
||||||
return "[options] <file>";
|
return "[options] <files> [--] [args]";
|
||||||
}
|
}
|
||||||
|
|
||||||
public void stop() {
|
public void stop() {
|
||||||
@ -86,30 +89,40 @@ public class RunCommand extends OptionParsingCommand {
|
|||||||
@Override
|
@Override
|
||||||
protected void run(OptionSet options) throws Exception {
|
protected void run(OptionSet options) throws Exception {
|
||||||
List<String> nonOptionArguments = options.nonOptionArguments();
|
List<String> nonOptionArguments = options.nonOptionArguments();
|
||||||
File file = getFileArgument(nonOptionArguments);
|
File[] files = getFileArguments(nonOptionArguments);
|
||||||
List<String> args = nonOptionArguments.subList(1, nonOptionArguments.size());
|
List<String> args = nonOptionArguments.subList(files.length,
|
||||||
|
nonOptionArguments.size());
|
||||||
|
|
||||||
if (options.has(this.editOption)) {
|
if (options.has(this.editOption)) {
|
||||||
Desktop.getDesktop().edit(file);
|
Desktop.getDesktop().edit(files[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
BootstrapRunnerConfiguration configuration = new BootstrapRunnerConfigurationAdapter(
|
BootstrapRunnerConfiguration configuration = new BootstrapRunnerConfigurationAdapter(
|
||||||
options);
|
options);
|
||||||
this.runner = new BootstrapRunner(configuration, file,
|
this.runner = new BootstrapRunner(configuration, files,
|
||||||
args.toArray(new String[args.size()]));
|
args.toArray(new String[args.size()]));
|
||||||
this.runner.compileAndRun();
|
this.runner.compileAndRun();
|
||||||
}
|
}
|
||||||
|
|
||||||
private File getFileArgument(List<String> nonOptionArguments) {
|
private File[] getFileArguments(List<String> nonOptionArguments) {
|
||||||
if (nonOptionArguments.size() == 0) {
|
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");
|
throw new RuntimeException("Please specify a file to run");
|
||||||
}
|
}
|
||||||
String filename = nonOptionArguments.get(0);
|
return files.toArray(new File[files.size()]);
|
||||||
File file = new File(filename);
|
|
||||||
if (!file.isFile() || !file.canRead()) {
|
|
||||||
throw new RuntimeException("Unable to read '" + filename + "'");
|
|
||||||
}
|
|
||||||
return file;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -35,13 +35,16 @@ import org.codehaus.groovy.control.SourceUnit;
|
|||||||
* resources.
|
* resources.
|
||||||
*
|
*
|
||||||
* @author Phillip Webb
|
* @author Phillip Webb
|
||||||
|
* @author Dave Syer
|
||||||
*/
|
*/
|
||||||
class ExtendedGroovyClassLoader extends GroovyClassLoader {
|
class ExtendedGroovyClassLoader extends GroovyClassLoader {
|
||||||
|
|
||||||
private Map<String, byte[]> classResources = new HashMap<String, byte[]>();
|
private Map<String, byte[]> classResources = new HashMap<String, byte[]>();
|
||||||
|
private CompilerConfiguration configuration;
|
||||||
|
|
||||||
public ExtendedGroovyClassLoader(ClassLoader loader, CompilerConfiguration config) {
|
public ExtendedGroovyClassLoader(ClassLoader loader, CompilerConfiguration config) {
|
||||||
super(loader, config);
|
super(loader, config);
|
||||||
|
this.configuration = config;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -54,8 +57,12 @@ class ExtendedGroovyClassLoader extends GroovyClassLoader {
|
|||||||
return resourceStream;
|
return resourceStream;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public CompilerConfiguration getConfiguration() {
|
||||||
|
return this.configuration;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected ClassCollector createCollector(CompilationUnit unit, SourceUnit su) {
|
public ClassCollector createCollector(CompilationUnit unit, SourceUnit su) {
|
||||||
InnerLoader loader = AccessController
|
InnerLoader loader = AccessController
|
||||||
.doPrivileged(new PrivilegedAction<InnerLoader>() {
|
.doPrivileged(new PrivilegedAction<InnerLoader>() {
|
||||||
@Override
|
@Override
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
package org.springframework.bootstrap.cli.compiler;
|
package org.springframework.bootstrap.cli.compiler;
|
||||||
|
|
||||||
import groovy.lang.GroovyClassLoader;
|
import groovy.lang.GroovyClassLoader;
|
||||||
|
import groovy.lang.GroovyClassLoader.ClassCollector;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@ -26,8 +27,10 @@ import java.util.List;
|
|||||||
import org.codehaus.groovy.ast.ClassNode;
|
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.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.SourceUnit;
|
import org.codehaus.groovy.control.SourceUnit;
|
||||||
import org.codehaus.groovy.control.customizers.CompilationCustomizer;
|
import org.codehaus.groovy.control.customizers.CompilationCustomizer;
|
||||||
import org.codehaus.groovy.control.customizers.ImportCustomizer;
|
import org.codehaus.groovy.control.customizers.ImportCustomizer;
|
||||||
@ -49,6 +52,7 @@ import org.springframework.bootstrap.cli.compiler.autoconfigure.SpringMvcCompile
|
|||||||
* <ul>
|
* <ul>
|
||||||
*
|
*
|
||||||
* @author Phillip Webb
|
* @author Phillip Webb
|
||||||
|
* @author Dave Syer
|
||||||
*/
|
*/
|
||||||
public class GroovyCompiler {
|
public class GroovyCompiler {
|
||||||
|
|
||||||
@ -79,24 +83,37 @@ public class GroovyCompiler {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Compile the specified Groovy source files, applying any
|
* Compile the specified Groovy source files, applying any
|
||||||
* {@link CompilerAutoConfiguration}s. All classes defined in the file will be
|
* {@link CompilerAutoConfiguration}s. All classes defined in the files will be
|
||||||
* returned from this method with the first item being the primary class (defined at
|
* returned from this method.
|
||||||
* the top of the file).
|
|
||||||
* @param file the file to compile
|
* @param file the file to compile
|
||||||
* @return compiled classes
|
* @return compiled classes
|
||||||
* @throws CompilationFailedException
|
* @throws CompilationFailedException
|
||||||
* @throws IOException
|
* @throws IOException
|
||||||
*/
|
*/
|
||||||
public Class<?>[] compile(File file) throws CompilationFailedException, IOException {
|
public Class<?>[] compile(File... file) throws CompilationFailedException,
|
||||||
|
IOException {
|
||||||
|
|
||||||
this.loader.clearCache();
|
this.loader.clearCache();
|
||||||
List<Class<?>> classes = new ArrayList<Class<?>>();
|
List<Class<?>> classes = new ArrayList<Class<?>>();
|
||||||
Class<?> mainClass = this.loader.parseClass(file);
|
|
||||||
for (Class<?> loadedClass : this.loader.getLoadedClasses()) {
|
CompilerConfiguration compilerConfiguration = this.loader.getConfiguration();
|
||||||
classes.add(loadedClass);
|
|
||||||
|
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()]);
|
return classes.toArray(new Class<?>[classes.size()]);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -37,7 +37,7 @@ public class BootstrapRunner {
|
|||||||
|
|
||||||
private BootstrapRunnerConfiguration configuration;
|
private BootstrapRunnerConfiguration configuration;
|
||||||
|
|
||||||
private final File file;
|
private final File[] files;
|
||||||
|
|
||||||
private final String[] args;
|
private final String[] args;
|
||||||
|
|
||||||
@ -50,13 +50,13 @@ public class BootstrapRunner {
|
|||||||
/**
|
/**
|
||||||
* Create a new {@link BootstrapRunner} instance.
|
* Create a new {@link BootstrapRunner} instance.
|
||||||
* @param configuration the configuration
|
* @param configuration the configuration
|
||||||
* @param file the file to compile/watch
|
* @param files the files to compile/watch
|
||||||
* @param args input arguments
|
* @param args input arguments
|
||||||
*/
|
*/
|
||||||
public BootstrapRunner(final BootstrapRunnerConfiguration configuration, File file,
|
public BootstrapRunner(final BootstrapRunnerConfiguration configuration,
|
||||||
String... args) {
|
File[] files, String... args) {
|
||||||
this.configuration = configuration;
|
this.configuration = configuration;
|
||||||
this.file = file;
|
this.files = files;
|
||||||
this.args = args;
|
this.args = args;
|
||||||
this.compiler = new GroovyCompiler(configuration);
|
this.compiler = new GroovyCompiler(configuration);
|
||||||
if (configuration.getLogLevel().intValue() <= Level.FINE.intValue()) {
|
if (configuration.getLogLevel().intValue() <= Level.FINE.intValue()) {
|
||||||
@ -75,9 +75,9 @@ public class BootstrapRunner {
|
|||||||
stop();
|
stop();
|
||||||
|
|
||||||
// Compile
|
// Compile
|
||||||
Class<?>[] classes = this.compiler.compile(this.file);
|
Class<?>[] classes = this.compiler.compile(this.files);
|
||||||
if (classes.length == 0) {
|
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
|
// Run in new thread to ensure that the context classloader is setup
|
||||||
@ -164,7 +164,13 @@ public class BootstrapRunner {
|
|||||||
private long previous;
|
private long previous;
|
||||||
|
|
||||||
public FileWatchThread() {
|
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);
|
setDaemon(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -173,10 +179,12 @@ public class BootstrapRunner {
|
|||||||
while (true) {
|
while (true) {
|
||||||
try {
|
try {
|
||||||
Thread.sleep(TimeUnit.SECONDS.toMillis(1));
|
Thread.sleep(TimeUnit.SECONDS.toMillis(1));
|
||||||
long current = BootstrapRunner.this.file.lastModified();
|
for (File file : BootstrapRunner.this.files) {
|
||||||
if (this.previous < current) {
|
long current = file.lastModified();
|
||||||
this.previous = current;
|
if (this.previous < current) {
|
||||||
compileAndRun();
|
this.previous = current;
|
||||||
|
compileAndRun();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (InterruptedException ex) {
|
} catch (InterruptedException ex) {
|
||||||
Thread.currentThread().interrupt();
|
Thread.currentThread().interrupt();
|
||||||
|
@ -59,7 +59,7 @@ public class SampleIntegrationTests {
|
|||||||
return this.output.toString();
|
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(
|
Future<RunCommand> future = Executors.newSingleThreadExecutor().submit(
|
||||||
new Callable<RunCommand>() {
|
new Callable<RunCommand>() {
|
||||||
@Override
|
@Override
|
||||||
@ -94,12 +94,23 @@ public class SampleIntegrationTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void jobSample() throws Exception {
|
public void jobSample() throws Exception {
|
||||||
start("samples/job.groovy");
|
start("samples/job.groovy", "foo=bar");
|
||||||
String output = getOutput();
|
String output = getOutput();
|
||||||
assertTrue("Wrong output: " + output,
|
assertTrue("Wrong output: " + output,
|
||||||
output.contains("completed with the following parameters"));
|
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
|
@Test
|
||||||
public void webSample() throws Exception {
|
public void webSample() throws Exception {
|
||||||
start("samples/web.groovy");
|
start("samples/web.groovy");
|
||||||
|
@ -20,11 +20,18 @@ import javax.sql.DataSource;
|
|||||||
|
|
||||||
import org.springframework.batch.support.DatabaseType;
|
import org.springframework.batch.support.DatabaseType;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.core.io.ResourceLoader;
|
import org.springframework.core.io.ResourceLoader;
|
||||||
import org.springframework.jdbc.datasource.init.DatabasePopulatorUtils;
|
import org.springframework.jdbc.datasource.init.DatabasePopulatorUtils;
|
||||||
import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;
|
import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the Spring Batch schema (ignoring errors, so should be idempotent).
|
||||||
|
*
|
||||||
|
* @author Dave Syer
|
||||||
|
*
|
||||||
|
*/
|
||||||
@Component
|
@Component
|
||||||
public class BatchDatabaseInitializer {
|
public class BatchDatabaseInitializer {
|
||||||
|
|
||||||
@ -34,6 +41,9 @@ public class BatchDatabaseInitializer {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private ResourceLoader resourceLoader;
|
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
|
@PostConstruct
|
||||||
protected void initialize() throws Exception {
|
protected void initialize() throws Exception {
|
||||||
String platform = DatabaseType.fromMetaData(this.dataSource).toString()
|
String platform = DatabaseType.fromMetaData(this.dataSource).toString()
|
||||||
@ -42,12 +52,9 @@ public class BatchDatabaseInitializer {
|
|||||||
platform = "hsqldb";
|
platform = "hsqldb";
|
||||||
}
|
}
|
||||||
ResourceDatabasePopulator populator = new ResourceDatabasePopulator();
|
ResourceDatabasePopulator populator = new ResourceDatabasePopulator();
|
||||||
populator
|
populator.addScript(this.resourceLoader.getResource(this.schemaLocation.replace(
|
||||||
.addScript(this.resourceLoader
|
"@@platform@@", platform)));
|
||||||
.getResource("org/springframework/batch/core/schema-" + platform
|
|
||||||
+ ".sql"));
|
|
||||||
populator.setContinueOnError(true);
|
populator.setContinueOnError(true);
|
||||||
DatabasePopulatorUtils.execute(populator, this.dataSource);
|
DatabasePopulatorUtils.execute(populator, this.dataSource);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
package org.springframework.bootstrap.context.annotation;
|
package org.springframework.bootstrap.context.annotation;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@ -47,8 +48,17 @@ public abstract class AutoConfigurationUtils {
|
|||||||
|
|
||||||
public static void storeBasePackages(ConfigurableListableBeanFactory beanFactory,
|
public static void storeBasePackages(ConfigurableListableBeanFactory beanFactory,
|
||||||
List<String> basePackages) {
|
List<String> basePackages) {
|
||||||
beanFactory.registerSingleton(BASE_PACKAGES_BEAN,
|
if (!beanFactory.containsBean(BASE_PACKAGES_BEAN)) {
|
||||||
Collections.unmodifiableList(basePackages));
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user