// Copyright © 2010, Esko Luontola <www.orfjackal.net> // This software is released under the Apache License 2.0. // The license text is at http://www.apache.org/licenses/LICENSE-2.0 package net.orfjackal.sbt.runner; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.util.text.StringUtil; import javax.swing.*; import java.io.*; import java.util.*; public class SbtRunner { private static final String PROMPT = "\n> "; private static final String SCALA_PROMPT = "\nscala> "; private static final String FAILED_TO_COMPILE_PROMPT = "Hit enter to retry or 'exit' to quit:"; private static final String PROMPT_AFTER_EMPTY_ACTION = "> "; private static final String ERROR_RUNNING_ACTION_PREFIX = "[error] Error running "; private static final String ERROR_SBT_010_PREFIX = "[error] Total time:"; private static final Logger LOG = Logger.getInstance("#orfjackal.sbt.runner.SbtRunner"); private final ProcessRunner sbt; private final String[] command; public SbtRunner(String javaCommand, File workingDir, File launcherJar, String[] vmParameters) { if (!workingDir.isDirectory()) { throw new IllegalArgumentException("Working directory does not exist: " + workingDir); } if (!launcherJar.isFile()) { throw new IllegalArgumentException("Launcher JAR file does not exist: " + launcherJar); } command = getCommand(javaCommand, launcherJar, vmParameters); sbt = new ProcessRunner(workingDir, command); } public final String getFormattedCommand() { StringBuilder sb = new StringBuilder(); for (String s : command) { if (s.contains(" ")) { sb.append("\"").append(s).append("\""); } else { sb.append(s); } sb.append(" "); } return sb.toString(); } private static String[] getCommand(String javaCommand, File launcherJar, String[] vmParameters) { List<String> command = new ArrayList<String>(); command.add(javaCommand); command.add("-Dsbt.log.noformat=true"); command.add("-Djline.terminal=jline.UnsupportedTerminal"); command.addAll(Arrays.asList(vmParameters)); command.addAll(Arrays.asList( "-jar", launcherJar.getAbsolutePath() )); LOG.info("SBT command line: " + StringUtil.join(command, " ")); return command.toArray(new String[command.size()]); } public OutputReader subscribeToOutput() { return sbt.subscribeToOutput(); } public void start(boolean wait) throws IOException { // TODO: detect if the directory does not have a project OutputReader output = sbt.subscribeToOutput(); sbt.start(); sbt.destroyOnShutdown(); if (wait) { output.waitForOutput(Arrays.asList(PROMPT, FAILED_TO_COMPILE_PROMPT), Arrays.<String>asList()); } output.close(); } public void start(boolean wait, final Runnable onStarted) throws IOException { // TODO: detect if the directory does not have a project final OutputReader output = sbt.subscribeToOutput(); sbt.start(); sbt.destroyOnShutdown(); if (wait) { output.waitForOutput(Arrays.asList(PROMPT, FAILED_TO_COMPILE_PROMPT), Arrays.<String>asList()); output.close(); onStarted.run(); } else { new Thread() { @Override public void run() { try { output.waitForOutput(Arrays.asList(PROMPT, FAILED_TO_COMPILE_PROMPT), Arrays.<String>asList()); output.close(); onStarted.run(); } catch (IOException e) { // ignore } } }.start(); } } public void destroy() { sbt.destroy(); } public boolean isAlive() { return sbt.isAlive(); } /** * @param action the SBT action to run, e.g. "compile" * @return false if an error was parsed from the output, true otherwise * @throws java.io.IOException */ public boolean execute(String action) throws IOException { OutputReader output = sbt.subscribeToOutput(); try { sbt.writeInput(action + "\n"); output.waitForOutput(Arrays.asList(PROMPT, SCALA_PROMPT, FAILED_TO_COMPILE_PROMPT), Arrays.asList(PROMPT_AFTER_EMPTY_ACTION)); } finally { output.close(); } boolean error = output.endOfOutputContains(ERROR_RUNNING_ACTION_PREFIX) || output.endOfOutputContains(ERROR_SBT_010_PREFIX); LOG.debug("completed: " + action + ", error: " + error); return !error; } }