diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/Command.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/Command.java index 3fb24b4b4c..6a04bd9747 100644 --- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/Command.java +++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/Command.java @@ -62,6 +62,85 @@ public interface Command { * @param args command arguments (this will not include the command itself) * @throws Exception */ - void run(String... args) throws Exception; + ExitStatus run(String... args) throws Exception; + + /** + * Encapsulation of the outcome of a command. + * + * @author Dave Syer + * + * @see ExitStatus#OK + * @see ExitStatus#ERROR + * + */ + public static class ExitStatus { + /** + * Generic "OK" exit status with zero exit code and hangup=fa;se + */ + public static ExitStatus OK = new ExitStatus(0, "OK"); + /** + * Generic "not OK" exit status with non-zero exit code and hangup=true + */ + public static ExitStatus ERROR = new ExitStatus(-1, "ERROR", true); + private int code; + private String name; + private boolean hangup; + + /** + * Create a new ExitStatus with an exit code and name as specified. + * + * @param code the exit code + * @param name the name + */ + public ExitStatus(int code, String name) { + this(code, name, false); + } + + /** + * @param code the exit code + * @param name the name + * @param hangup true if it is OK for the caller to hangup + */ + public ExitStatus(int code, String name, boolean hangup) { + this.code = code; + this.name = name; + this.hangup = hangup; + } + + /** + * An exit code appropriate for use in System.exit() + * @return an exit code + */ + public int getCode() { + return code; + } + + /** + * A name describing the outcome + * @return a name + */ + public String getName() { + return name; + } + + /** + * Flag to signal that the caller can (or should) hangup. A server process with + * non-daemon threads should set this to false. + * @return the flag + */ + public boolean isHangup() { + return hangup; + } + + /** + * Convert the existing code to a hangup. + * + * @return a new ExitStatus with hangup=true + */ + public ExitStatus hangup() { + return new ExitStatus(this.code, this.name, true); + } + + } } diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/CommandRunner.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/CommandRunner.java index 2afdabdbb4..5c93f331f2 100644 --- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/CommandRunner.java +++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/CommandRunner.java @@ -24,6 +24,7 @@ import java.util.Iterator; import java.util.List; import java.util.Set; +import org.springframework.boot.cli.command.Command.ExitStatus; import org.springframework.boot.cli.util.Log; import org.springframework.util.Assert; import org.springframework.util.StringUtils; @@ -166,8 +167,9 @@ public class CommandRunner implements Iterable { System.setProperty("debug", "true"); } try { - run(argsWithoutDebugFlags); - return 0; + ExitStatus result = run(argsWithoutDebugFlags); + // The caller will hang up if it gets a non-zero status + return result==null ? 0 : result.isHangup() ? (result.getCode()>0 ? result.getCode() : 1) : 0; } catch (NoArgumentsException ex) { showUsage(); @@ -197,7 +199,7 @@ public class CommandRunner implements Iterable { * @param args the arguments * @throws Exception */ - protected void run(String... args) throws Exception { + protected ExitStatus run(String... args) throws Exception { if (args.length == 0) { throw new NoArgumentsException(); } @@ -209,7 +211,7 @@ public class CommandRunner implements Iterable { } beforeRun(command); try { - command.run(commandArguments); + return command.run(commandArguments); } finally { afterRun(command); diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/OptionParsingCommand.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/OptionParsingCommand.java index fae15a26a5..199cbcc652 100644 --- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/OptionParsingCommand.java +++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/OptionParsingCommand.java @@ -48,8 +48,8 @@ public abstract class OptionParsingCommand extends AbstractCommand { } @Override - public final void run(String... args) throws Exception { - this.handler.run(args); + public final ExitStatus run(String... args) throws Exception { + return this.handler.run(args); } protected OptionHandler getHandler() { diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/core/HelpCommand.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/core/HelpCommand.java index 8da6dc2662..3722bf919a 100644 --- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/core/HelpCommand.java +++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/core/HelpCommand.java @@ -85,7 +85,7 @@ public class HelpCommand extends AbstractCommand { } @Override - public void run(String... args) throws Exception { + public ExitStatus run(String... args) throws Exception { if (args.length == 0) { throw new NoHelpCommandArgumentsException(); } @@ -103,7 +103,7 @@ public class HelpCommand extends AbstractCommand { if (command.getHelp() != null) { Log.info(command.getHelp()); } - return; + return ExitStatus.OK; } } throw new NoSuchCommandException(commandName); diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/core/HintCommand.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/core/HintCommand.java index 1ee18232ef..2ea18f1e76 100644 --- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/core/HintCommand.java +++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/core/HintCommand.java @@ -42,7 +42,7 @@ public class HintCommand extends AbstractCommand { } @Override - public void run(String... args) throws Exception { + public ExitStatus run(String... args) throws Exception { try { int index = (args.length == 0 ? 0 : Integer.valueOf(args[0]) - 1); List arguments = new ArrayList(args.length); @@ -64,7 +64,9 @@ public class HintCommand extends AbstractCommand { } catch (Exception ex) { // Swallow and provide no hints + return ExitStatus.ERROR; } + return ExitStatus.OK; } private void showCommandHints(String starting) { diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/core/VersionCommand.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/core/VersionCommand.java index 6efa3ac251..59bfac001b 100644 --- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/core/VersionCommand.java +++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/core/VersionCommand.java @@ -32,8 +32,9 @@ public class VersionCommand extends AbstractCommand { } @Override - public void run(String... args) { + public ExitStatus run(String... args) { Log.info("Spring CLI v" + getClass().getPackage().getImplementationVersion()); + return ExitStatus.OK; } } diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/grab/GrabCommand.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/grab/GrabCommand.java index df593b725a..7607221e74 100644 --- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/grab/GrabCommand.java +++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/grab/GrabCommand.java @@ -45,7 +45,7 @@ public class GrabCommand extends OptionParsingCommand { private static final class GrabOptionHandler extends CompilerOptionHandler { @Override - protected void run(OptionSet options) throws Exception { + protected ExitStatus run(OptionSet options) throws Exception { SourceOptions sourceOptions = new SourceOptions(options); List repositoryConfiguration = RepositoryConfigurationFactory @@ -60,6 +60,7 @@ public class GrabCommand extends OptionParsingCommand { GroovyCompiler groovyCompiler = new GroovyCompiler(configuration); groovyCompiler.compile(sourceOptions.getSourcesArray()); + return ExitStatus.OK; } } 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 392645a4ed..c3661b6f10 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 @@ -103,7 +103,7 @@ public class JarCommand extends OptionParsingCommand { } @Override - protected void run(OptionSet options) throws Exception { + protected ExitStatus run(OptionSet options) throws Exception { List nonOptionArguments = new ArrayList( options.nonOptionArguments()); Assert.isTrue(nonOptionArguments.size() >= 2, @@ -127,6 +127,7 @@ public class JarCommand extends OptionParsingCommand { dependencies.removeAll(classpath); writeJar(output, compiledClasses, classpathEntries, dependencies); + return ExitStatus.OK; } private void deleteIfExists(File file) { diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/options/OptionHandler.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/options/OptionHandler.java index ca3d9dbbe0..1c6e1dc0a0 100644 --- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/options/OptionHandler.java +++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/options/OptionHandler.java @@ -38,6 +38,7 @@ import joptsimple.OptionParser; import joptsimple.OptionSet; import joptsimple.OptionSpecBuilder; +import org.springframework.boot.cli.command.Command.ExitStatus; import org.springframework.boot.cli.command.OptionParsingCommand; /** @@ -80,7 +81,7 @@ public class OptionHandler { this.closure = closure; } - public final void run(String... args) throws Exception { + public final ExitStatus run(String... args) throws Exception { String[] argsToUse = args.clone(); for (int i = 0; i < argsToUse.length; i++) { if ("-cp".equals(argsToUse[i])) { @@ -88,18 +89,29 @@ public class OptionHandler { } } OptionSet options = getParser().parse(args); - run(options); + return run(options); } /** * Run the command using the specified parsed {@link OptionSet}. * @param options the parsed option set + * @return an ExitStatus * @throws Exception */ - protected void run(OptionSet options) throws Exception { + protected ExitStatus run(OptionSet options) throws Exception { if (this.closure != null) { - this.closure.call(options); + Object result = this.closure.call(options); + if (result instanceof ExitStatus) { + return (ExitStatus) result; + } + if (result instanceof Boolean) { + return (Boolean) result ? ExitStatus.OK : ExitStatus.ERROR; + } + if (result instanceof Integer) { + return new ExitStatus((Integer) result, "Finished"); + } } + return ExitStatus.OK; } public String getHelp() { diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/run/RunCommand.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/run/RunCommand.java index ec29f431ad..018a0b36df 100644 --- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/run/RunCommand.java +++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/run/RunCommand.java @@ -85,7 +85,7 @@ public class RunCommand extends OptionParsingCommand { } @Override - protected synchronized void run(OptionSet options) throws Exception { + protected synchronized ExitStatus run(OptionSet options) throws Exception { if (this.runner != null) { throw new RuntimeException( @@ -105,6 +105,8 @@ public class RunCommand extends OptionParsingCommand { this.runner = new SpringApplicationRunner(configuration, sourceOptions.getSourcesArray(), sourceOptions.getArgsArray()); this.runner.compileAndRun(); + + return ExitStatus.OK; } /** diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/shell/ClearCommand.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/shell/ClearCommand.java index 9eb94fd4e9..4aa367a421 100644 --- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/shell/ClearCommand.java +++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/shell/ClearCommand.java @@ -36,9 +36,10 @@ class ClearCommand extends AbstractCommand { } @Override - public void run(String... args) throws Exception { + public ExitStatus run(String... args) throws Exception { this.consoleReader.setPrompt(""); this.consoleReader.clearScreen(); + return ExitStatus.OK; } } diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/shell/ExitCommand.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/shell/ExitCommand.java index 298b42e53e..adf445de35 100644 --- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/shell/ExitCommand.java +++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/shell/ExitCommand.java @@ -31,7 +31,7 @@ class ExitCommand extends AbstractCommand { } @Override - public void run(String... args) throws Exception { + public ExitStatus run(String... args) throws Exception { throw new ShellExitException(); } 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 f05dac3990..4d210d2aaa 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 @@ -67,7 +67,7 @@ class ForkProcessCommand extends RunProcessCommand { } @Override - public void run(String... args) throws Exception { + public ExitStatus run(String... args) throws Exception { List fullArgs = new ArrayList(); fullArgs.add("-cp"); fullArgs.add(System.getProperty("java.class.path")); @@ -75,6 +75,7 @@ class ForkProcessCommand extends RunProcessCommand { fullArgs.add(this.command.getName()); fullArgs.addAll(Arrays.asList(args)); run(fullArgs); + return ExitStatus.OK; } } diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/shell/PromptCommand.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/shell/PromptCommand.java index 4c147bc1b5..f0dfd5e25b 100644 --- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/shell/PromptCommand.java +++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/shell/PromptCommand.java @@ -35,7 +35,7 @@ public class PromptCommand extends AbstractCommand { } @Override - public void run(String... strings) throws Exception { + public ExitStatus run(String... strings) throws Exception { if (strings.length > 0) { for (String string : strings) { this.prompts.pushPrompt(string + " "); @@ -44,6 +44,7 @@ public class PromptCommand extends AbstractCommand { else { this.prompts.popPrompt(); } + return ExitStatus.OK; } } diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/shell/RunProcessCommand.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/shell/RunProcessCommand.java index d040e99875..3dd2baabc3 100644 --- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/shell/RunProcessCommand.java +++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/shell/RunProcessCommand.java @@ -41,13 +41,19 @@ class RunProcessCommand extends AbstractCommand { } @Override - public void run(String... args) throws Exception { - run(Arrays.asList(args)); + public ExitStatus run(String... args) throws Exception { + return run(Arrays.asList(args)); } - protected void run(Collection args) throws IOException { + protected ExitStatus run(Collection args) throws IOException { this.process = new RunProcess(this.command); - this.process.run(args.toArray(new String[args.size()])); + int code = this.process.run(args.toArray(new String[args.size()])); + if (code == 0) { + return ExitStatus.OK; + } + else { + return new ExitStatus(code, "EXTERNAL_ERROR"); + } } public boolean handleSigInt() { diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/shell/ShellCommand.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/shell/ShellCommand.java index e22cc664f2..284ab800bc 100644 --- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/shell/ShellCommand.java +++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/shell/ShellCommand.java @@ -32,8 +32,9 @@ public class ShellCommand extends AbstractCommand { } @Override - public void run(String... args) throws Exception { + public ExitStatus run(String... args) throws Exception { new Shell().run(); + return ExitStatus.OK; } } diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/test/TestCommand.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/test/TestCommand.java index fc350e1944..bb00b3289e 100644 --- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/test/TestCommand.java +++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/test/TestCommand.java @@ -17,7 +17,6 @@ package org.springframework.boot.cli.command.test; import joptsimple.OptionSet; -import joptsimple.OptionSpec; import org.springframework.boot.cli.command.Command; import org.springframework.boot.cli.command.OptionParsingCommand; @@ -47,22 +46,14 @@ public class TestCommand extends OptionParsingCommand { private TestRunner runner; @Override - protected void doOptions() { - option("nohup", - "Flag to indicate that the JVM should not exit when tests are finished"); - } - - @Override - protected void run(OptionSet options) throws Exception { + protected ExitStatus run(OptionSet options) throws Exception { SourceOptions sourceOptions = new SourceOptions(options); TestRunnerConfiguration configuration = new TestRunnerConfigurationAdapter( options, this); this.runner = new TestRunner(configuration, sourceOptions.getSourcesArray(), sourceOptions.getArgsArray()); this.runner.compileAndRunTests(); - if (!options.has("nohup")) { - System.exit(0); // TODO: non-zero if test fails? - } + return ExitStatus.OK.hangup(); } /** @@ -77,5 +68,7 @@ public class TestCommand extends OptionParsingCommand { super(options, optionHandler); } } + } + } diff --git a/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/autoconfigure/SpringBootCompilerAutoConfiguration.java b/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/autoconfigure/SpringBootCompilerAutoConfiguration.java index 280a6747ad..47c3014f63 100644 --- a/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/autoconfigure/SpringBootCompilerAutoConfiguration.java +++ b/spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/autoconfigure/SpringBootCompilerAutoConfiguration.java @@ -67,6 +67,8 @@ public class SpringBootCompilerAutoConfiguration extends CompilerAutoConfigurati "org.springframework.boot.context.properties.ConfigurationProperties", "org.springframework.boot.context.properties.EnableConfigurationProperties", "org.springframework.boot.autoconfigure.EnableAutoConfiguration", + "org.springframework.boot.context.properties.ConfigurationProperties", + "org.springframework.boot.context.properties.EnableConfigurationProperties", "org.springframework.boot.groovy.GrabMetadata"); imports.addStarImports("org.springframework.stereotype", "org.springframework.scheduling.annotation"); diff --git a/spring-boot-cli/src/test/java/cli/command/CustomCommand.java b/spring-boot-cli/src/test/java/cli/command/CustomCommand.java index 24c9a36382..e2dc0cf954 100644 --- a/spring-boot-cli/src/test/java/cli/command/CustomCommand.java +++ b/spring-boot-cli/src/test/java/cli/command/CustomCommand.java @@ -28,8 +28,9 @@ public class CustomCommand extends AbstractCommand { } @Override - public void run(String... args) throws Exception { + public ExitStatus run(String... args) throws Exception { System.err.println("Custom Command Hello"); + return ExitStatus.OK; } } diff --git a/spring-boot-cli/src/test/java/org/springframework/boot/cli/CliTester.java b/spring-boot-cli/src/test/java/org/springframework/boot/cli/CliTester.java index 1f7a8037f9..a11a2a041d 100644 --- a/spring-boot-cli/src/test/java/org/springframework/boot/cli/CliTester.java +++ b/spring-boot-cli/src/test/java/org/springframework/boot/cli/CliTester.java @@ -75,10 +75,7 @@ public class CliTester implements TestRule { } public String test(String... args) throws Exception { - String[] argsToUse = new String[args.length + 1]; - System.arraycopy(args, 0, argsToUse, 1, args.length); - argsToUse[0] = "--nohup"; - Future future = submitCommand(new TestCommand(), argsToUse); + Future future = submitCommand(new TestCommand(), args); this.commands.add(future.get(this.timeout, TimeUnit.MILLISECONDS)); return getOutput(); } diff --git a/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/RunProcess.java b/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/RunProcess.java index 3a9f199209..deb5767bf2 100644 --- a/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/RunProcess.java +++ b/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/RunProcess.java @@ -48,11 +48,11 @@ public class RunProcess { this.command = command; } - public void run(String... args) throws IOException { - run(Arrays.asList(args)); + public int run(String... args) throws IOException { + return run(Arrays.asList(args)); } - protected void run(Collection args) throws IOException { + protected int run(Collection args) throws IOException { ProcessBuilder builder = new ProcessBuilder(this.command); builder.command().addAll(args); builder.redirectErrorStream(true); @@ -74,6 +74,12 @@ public class RunProcess { catch (InterruptedException ex) { Thread.currentThread().interrupt(); } + try { + return this.process.exitValue(); + } + catch (IllegalThreadStateException e) { + return 1; + } } finally { this.endTime = System.currentTimeMillis();