mirror of https://github.com/sbt/sbt.git
remove process
This commit is contained in:
parent
871b4f4eef
commit
4629053277
|
|
@ -1,3 +0,0 @@
|
|||
Simple Build Tool: Process Component
|
||||
Copyright 2008, 2009, 2010 Mark Harrah, Vesa Vilhonen
|
||||
Licensed under BSD-style license (see LICENSE)
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
/* sbt -- Simple Build Tool
|
||||
* Copyright 2012 Eugene Vigdorchik
|
||||
*/
|
||||
package sbt
|
||||
|
||||
import java.lang.{ ProcessBuilder => JProcessBuilder }
|
||||
|
||||
/** On java 7, inherit System.in for a ProcessBuilder. */
|
||||
private[sbt] object InheritInput {
|
||||
def apply(p: JProcessBuilder): Boolean = (redirectInput, inherit) match {
|
||||
case (Some(m), Some(f)) =>
|
||||
m.invoke(p, f); true
|
||||
case _ => false
|
||||
}
|
||||
|
||||
private[this] val pbClass = Class.forName("java.lang.ProcessBuilder")
|
||||
private[this] val redirectClass = pbClass.getClasses find (_.getSimpleName == "Redirect")
|
||||
|
||||
private[this] val redirectInput = redirectClass map (pbClass.getMethod("redirectInput", _))
|
||||
private[this] val inherit = redirectClass map (_ getField "INHERIT" get null)
|
||||
}
|
||||
|
|
@ -1,221 +0,0 @@
|
|||
/* sbt -- Simple Build Tool
|
||||
* Copyright 2009 Mark Harrah
|
||||
*/
|
||||
package sbt
|
||||
|
||||
import java.lang.{ Process => JProcess, ProcessBuilder => JProcessBuilder }
|
||||
import java.io.{ Closeable, File, IOException }
|
||||
import java.io.{ BufferedReader, InputStream, InputStreamReader, OutputStream, PipedInputStream, PipedOutputStream }
|
||||
import java.net.URL
|
||||
|
||||
trait ProcessExtra {
|
||||
import Process._
|
||||
implicit def builderToProcess(builder: JProcessBuilder): ProcessBuilder = apply(builder)
|
||||
implicit def fileToProcess(file: File): FilePartialBuilder = apply(file)
|
||||
implicit def urlToProcess(url: URL): URLPartialBuilder = apply(url)
|
||||
@deprecated("Use string interpolation", "0.13.0")
|
||||
implicit def xmlToProcess(command: scala.xml.Elem): ProcessBuilder = apply(command)
|
||||
implicit def buildersToProcess[T](builders: Seq[T])(implicit convert: T => SourcePartialBuilder): Seq[SourcePartialBuilder] = applySeq(builders)
|
||||
|
||||
implicit def stringToProcess(command: String): ProcessBuilder = apply(command)
|
||||
implicit def stringSeqToProcess(command: Seq[String]): ProcessBuilder = apply(command)
|
||||
}
|
||||
|
||||
/** Methods for constructing simple commands that can then be combined. */
|
||||
object Process extends ProcessExtra {
|
||||
def apply(command: String): ProcessBuilder = apply(command, None)
|
||||
|
||||
def apply(command: Seq[String]): ProcessBuilder = apply(command.toArray, None)
|
||||
|
||||
def apply(command: String, arguments: Seq[String]): ProcessBuilder = apply(command :: arguments.toList, None)
|
||||
/** create ProcessBuilder with working dir set to File and extra environment variables */
|
||||
def apply(command: String, cwd: File, extraEnv: (String, String)*): ProcessBuilder =
|
||||
apply(command, Some(cwd), extraEnv: _*)
|
||||
/** create ProcessBuilder with working dir set to File and extra environment variables */
|
||||
def apply(command: Seq[String], cwd: File, extraEnv: (String, String)*): ProcessBuilder =
|
||||
apply(command, Some(cwd), extraEnv: _*)
|
||||
/** create ProcessBuilder with working dir optionally set to File and extra environment variables */
|
||||
def apply(command: String, cwd: Option[File], extraEnv: (String, String)*): ProcessBuilder = {
|
||||
apply(command.split("""\s+"""), cwd, extraEnv: _*)
|
||||
// not smart to use this on windows, because CommandParser uses \ to escape ".
|
||||
/*CommandParser.parse(command) match {
|
||||
case Left(errorMsg) => error(errorMsg)
|
||||
case Right((cmd, args)) => apply(cmd :: args, cwd, extraEnv : _*)
|
||||
}*/
|
||||
}
|
||||
/** create ProcessBuilder with working dir optionally set to File and extra environment variables */
|
||||
def apply(command: Seq[String], cwd: Option[File], extraEnv: (String, String)*): ProcessBuilder = {
|
||||
val jpb = new JProcessBuilder(command.toArray: _*)
|
||||
cwd.foreach(jpb directory _)
|
||||
extraEnv.foreach { case (k, v) => jpb.environment.put(k, v) }
|
||||
apply(jpb)
|
||||
}
|
||||
def apply(builder: JProcessBuilder): ProcessBuilder = new SimpleProcessBuilder(builder)
|
||||
def apply(file: File): FilePartialBuilder = new FileBuilder(file)
|
||||
def apply(url: URL): URLPartialBuilder = new URLBuilder(url)
|
||||
@deprecated("Use string interpolation", "0.13.0")
|
||||
def apply(command: scala.xml.Elem): ProcessBuilder = apply(command.text.trim)
|
||||
def applySeq[T](builders: Seq[T])(implicit convert: T => SourcePartialBuilder): Seq[SourcePartialBuilder] = builders.map(convert)
|
||||
|
||||
def apply(value: Boolean): ProcessBuilder = apply(value.toString, if (value) 0 else 1)
|
||||
def apply(name: String, exitValue: => Int): ProcessBuilder = new DummyProcessBuilder(name, exitValue)
|
||||
|
||||
def cat(file: SourcePartialBuilder, files: SourcePartialBuilder*): ProcessBuilder = cat(file :: files.toList)
|
||||
def cat(files: Seq[SourcePartialBuilder]): ProcessBuilder =
|
||||
{
|
||||
require(files.nonEmpty)
|
||||
files.map(_.cat).reduceLeft(_ #&& _)
|
||||
}
|
||||
}
|
||||
|
||||
trait SourcePartialBuilder extends NotNull {
|
||||
/** Writes the output stream of this process to the given file. */
|
||||
def #>(f: File): ProcessBuilder = toFile(f, false)
|
||||
/** Appends the output stream of this process to the given file. */
|
||||
def #>>(f: File): ProcessBuilder = toFile(f, true)
|
||||
/**
|
||||
* Writes the output stream of this process to the given OutputStream. The
|
||||
* argument is call-by-name, so the stream is recreated, written, and closed each
|
||||
* time this process is executed.
|
||||
*/
|
||||
def #>(out: => OutputStream): ProcessBuilder = #>(new OutputStreamBuilder(out))
|
||||
def #>(b: ProcessBuilder): ProcessBuilder = new PipedProcessBuilder(toSource, b, false, ExitCodes.firstIfNonzero)
|
||||
private def toFile(f: File, append: Boolean) = #>(new FileOutput(f, append))
|
||||
def cat = toSource
|
||||
protected def toSource: ProcessBuilder
|
||||
}
|
||||
trait SinkPartialBuilder extends NotNull {
|
||||
/** Reads the given file into the input stream of this process. */
|
||||
def #<(f: File): ProcessBuilder = #<(new FileInput(f))
|
||||
/** Reads the given URL into the input stream of this process. */
|
||||
def #<(f: URL): ProcessBuilder = #<(new URLInput(f))
|
||||
/**
|
||||
* Reads the given InputStream into the input stream of this process. The
|
||||
* argument is call-by-name, so the stream is recreated, read, and closed each
|
||||
* time this process is executed.
|
||||
*/
|
||||
def #<(in: => InputStream): ProcessBuilder = #<(new InputStreamBuilder(in))
|
||||
def #<(b: ProcessBuilder): ProcessBuilder = new PipedProcessBuilder(b, toSink, false, ExitCodes.firstIfNonzero)
|
||||
protected def toSink: ProcessBuilder
|
||||
}
|
||||
|
||||
trait URLPartialBuilder extends SourcePartialBuilder
|
||||
trait FilePartialBuilder extends SinkPartialBuilder with SourcePartialBuilder {
|
||||
def #<<(f: File): ProcessBuilder
|
||||
def #<<(u: URL): ProcessBuilder
|
||||
def #<<(i: => InputStream): ProcessBuilder
|
||||
def #<<(p: ProcessBuilder): ProcessBuilder
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a process that is running or has finished running.
|
||||
* It may be a compound process with several underlying native processes (such as 'a #&& b`).
|
||||
*/
|
||||
trait Process extends NotNull {
|
||||
/** Blocks until this process exits and returns the exit code.*/
|
||||
def exitValue(): Int
|
||||
/** Destroys this process. */
|
||||
def destroy(): Unit
|
||||
}
|
||||
/** Represents a runnable process. */
|
||||
trait ProcessBuilder extends SourcePartialBuilder with SinkPartialBuilder {
|
||||
/**
|
||||
* Starts the process represented by this builder, blocks until it exits, and returns the output as a String. Standard error is
|
||||
* sent to the console. If the exit code is non-zero, an exception is thrown.
|
||||
*/
|
||||
def !! : String
|
||||
/**
|
||||
* Starts the process represented by this builder, blocks until it exits, and returns the output as a String. Standard error is
|
||||
* sent to the provided ProcessLogger. If the exit code is non-zero, an exception is thrown.
|
||||
*/
|
||||
def !!(log: ProcessLogger): String
|
||||
/**
|
||||
* Starts the process represented by this builder. The output is returned as a Stream that blocks when lines are not available
|
||||
* but the process has not completed. Standard error is sent to the console. If the process exits with a non-zero value,
|
||||
* the Stream will provide all lines up to termination and then throw an exception.
|
||||
*/
|
||||
def lines: Stream[String]
|
||||
/**
|
||||
* Starts the process represented by this builder. The output is returned as a Stream that blocks when lines are not available
|
||||
* but the process has not completed. Standard error is sent to the provided ProcessLogger. If the process exits with a non-zero value,
|
||||
* the Stream will provide all lines up to termination but will not throw an exception.
|
||||
*/
|
||||
def lines(log: ProcessLogger): Stream[String]
|
||||
/**
|
||||
* Starts the process represented by this builder. The output is returned as a Stream that blocks when lines are not available
|
||||
* but the process has not completed. Standard error is sent to the console. If the process exits with a non-zero value,
|
||||
* the Stream will provide all lines up to termination but will not throw an exception.
|
||||
*/
|
||||
def lines_! : Stream[String]
|
||||
/**
|
||||
* Starts the process represented by this builder. The output is returned as a Stream that blocks when lines are not available
|
||||
* but the process has not completed. Standard error is sent to the provided ProcessLogger. If the process exits with a non-zero value,
|
||||
* the Stream will provide all lines up to termination but will not throw an exception.
|
||||
*/
|
||||
def lines_!(log: ProcessLogger): Stream[String]
|
||||
/**
|
||||
* Starts the process represented by this builder, blocks until it exits, and returns the exit code. Standard output and error are
|
||||
* sent to the console.
|
||||
*/
|
||||
def ! : Int
|
||||
/**
|
||||
* Starts the process represented by this builder, blocks until it exits, and returns the exit code. Standard output and error are
|
||||
* sent to the given ProcessLogger.
|
||||
*/
|
||||
def !(log: ProcessLogger): Int
|
||||
/**
|
||||
* Starts the process represented by this builder, blocks until it exits, and returns the exit code. Standard output and error are
|
||||
* sent to the console. The newly started process reads from standard input of the current process.
|
||||
*/
|
||||
def !< : Int
|
||||
/**
|
||||
* Starts the process represented by this builder, blocks until it exits, and returns the exit code. Standard output and error are
|
||||
* sent to the given ProcessLogger. The newly started process reads from standard input of the current process.
|
||||
*/
|
||||
def !<(log: ProcessLogger): Int
|
||||
/** Starts the process represented by this builder. Standard output and error are sent to the console.*/
|
||||
def run(): Process
|
||||
/** Starts the process represented by this builder. Standard output and error are sent to the given ProcessLogger.*/
|
||||
def run(log: ProcessLogger): Process
|
||||
/** Starts the process represented by this builder. I/O is handled by the given ProcessIO instance.*/
|
||||
def run(io: ProcessIO): Process
|
||||
/**
|
||||
* Starts the process represented by this builder. Standard output and error are sent to the console.
|
||||
* The newly started process reads from standard input of the current process if `connectInput` is true.
|
||||
*/
|
||||
def run(connectInput: Boolean): Process
|
||||
/**
|
||||
* Starts the process represented by this builder, blocks until it exits, and returns the exit code. Standard output and error are
|
||||
* sent to the given ProcessLogger.
|
||||
* The newly started process reads from standard input of the current process if `connectInput` is true.
|
||||
*/
|
||||
def run(log: ProcessLogger, connectInput: Boolean): Process
|
||||
|
||||
def runBuffered(log: ProcessLogger, connectInput: Boolean): Process
|
||||
|
||||
/** Constructs a command that runs this command first and then `other` if this command succeeds.*/
|
||||
def #&&(other: ProcessBuilder): ProcessBuilder
|
||||
/** Constructs a command that runs this command first and then `other` if this command does not succeed.*/
|
||||
def #||(other: ProcessBuilder): ProcessBuilder
|
||||
/**
|
||||
* Constructs a command that will run this command and pipes the output to `other`.
|
||||
* `other` must be a simple command.
|
||||
* The exit code will be that of `other` regardless of whether this command succeeds.
|
||||
*/
|
||||
def #|(other: ProcessBuilder): ProcessBuilder
|
||||
/** Constructs a command that will run this command and then `other`. The exit code will be the exit code of `other`.*/
|
||||
def ###(other: ProcessBuilder): ProcessBuilder
|
||||
|
||||
def canPipeTo: Boolean
|
||||
}
|
||||
/** Each method will be called in a separate thread.*/
|
||||
final class ProcessIO(val writeInput: OutputStream => Unit, val processOutput: InputStream => Unit, val processError: InputStream => Unit, val inheritInput: JProcessBuilder => Boolean) extends NotNull {
|
||||
def withOutput(process: InputStream => Unit): ProcessIO = new ProcessIO(writeInput, process, processError, inheritInput)
|
||||
def withError(process: InputStream => Unit): ProcessIO = new ProcessIO(writeInput, processOutput, process, inheritInput)
|
||||
def withInput(write: OutputStream => Unit): ProcessIO = new ProcessIO(write, processOutput, processError, inheritInput)
|
||||
}
|
||||
trait ProcessLogger {
|
||||
def info(s: => String): Unit
|
||||
def error(s: => String): Unit
|
||||
def buffer[T](f: => T): T
|
||||
}
|
||||
|
|
@ -1,436 +0,0 @@
|
|||
/* sbt -- Simple Build Tool
|
||||
* Copyright 2009 Mark Harrah, Vesa Vilhonen
|
||||
*/
|
||||
package sbt
|
||||
|
||||
import java.lang.{ Process => JProcess, ProcessBuilder => JProcessBuilder }
|
||||
import java.io.{ BufferedReader, Closeable, InputStream, InputStreamReader, IOException, OutputStream, PrintStream }
|
||||
import java.io.{ FilterInputStream, FilterOutputStream, PipedInputStream, PipedOutputStream }
|
||||
import java.io.{ File, FileInputStream, FileOutputStream }
|
||||
import java.net.URL
|
||||
|
||||
/** Runs provided code in a new Thread and returns the Thread instance. */
|
||||
private object Spawn {
|
||||
def apply(f: => Unit): Thread = apply(f, false)
|
||||
def apply(f: => Unit, daemon: Boolean): Thread =
|
||||
{
|
||||
val thread = new Thread() { override def run() = { f } }
|
||||
thread.setDaemon(daemon)
|
||||
thread.start()
|
||||
thread
|
||||
}
|
||||
}
|
||||
private object Future {
|
||||
def apply[T](f: => T): () => T =
|
||||
{
|
||||
val result = new SyncVar[Either[Throwable, T]]
|
||||
def run(): Unit =
|
||||
try { result.set(Right(f)) }
|
||||
catch { case e: Exception => result.set(Left(e)) }
|
||||
Spawn(run)
|
||||
() =>
|
||||
result.get match {
|
||||
case Right(value) => value
|
||||
case Left(exception) => throw exception
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object BasicIO {
|
||||
def apply(buffer: StringBuffer, log: Option[ProcessLogger], withIn: Boolean) = new ProcessIO(input(withIn), processFully(buffer), getErr(log), inheritInput(withIn))
|
||||
def apply(log: ProcessLogger, withIn: Boolean) = new ProcessIO(input(withIn), processInfoFully(log), processErrFully(log), inheritInput(withIn))
|
||||
|
||||
def getErr(log: Option[ProcessLogger]) = log match { case Some(lg) => processErrFully(lg); case None => toStdErr }
|
||||
|
||||
private def processErrFully(log: ProcessLogger) = processFully(s => log.error(s))
|
||||
private def processInfoFully(log: ProcessLogger) = processFully(s => log.info(s))
|
||||
|
||||
def closeOut = (_: OutputStream).close()
|
||||
final val BufferSize = 8192
|
||||
final val Newline = System.getProperty("line.separator")
|
||||
|
||||
def close(c: java.io.Closeable) = try { c.close() } catch { case _: java.io.IOException => () }
|
||||
def processFully(buffer: Appendable): InputStream => Unit = processFully(appendLine(buffer))
|
||||
def processFully(processLine: String => Unit): InputStream => Unit =
|
||||
in =>
|
||||
{
|
||||
val reader = new BufferedReader(new InputStreamReader(in))
|
||||
processLinesFully(processLine)(reader.readLine)
|
||||
reader.close()
|
||||
}
|
||||
def processLinesFully(processLine: String => Unit)(readLine: () => String): Unit = {
|
||||
def readFully(): Unit = {
|
||||
val line = readLine()
|
||||
if (line != null) {
|
||||
processLine(line)
|
||||
readFully()
|
||||
}
|
||||
}
|
||||
readFully()
|
||||
}
|
||||
def connectToIn(o: OutputStream): Unit = transferFully(Uncloseable protect System.in, o)
|
||||
def input(connect: Boolean): OutputStream => Unit = if (connect) connectToIn else closeOut
|
||||
def standard(connectInput: Boolean): ProcessIO = standard(input(connectInput), inheritInput(connectInput))
|
||||
def standard(in: OutputStream => Unit, inheritIn: JProcessBuilder => Boolean): ProcessIO = new ProcessIO(in, toStdOut, toStdErr, inheritIn)
|
||||
|
||||
def toStdErr = (in: InputStream) => transferFully(in, System.err)
|
||||
def toStdOut = (in: InputStream) => transferFully(in, System.out)
|
||||
|
||||
def transferFully(in: InputStream, out: OutputStream): Unit =
|
||||
try { transferFullyImpl(in, out) }
|
||||
catch { case _: InterruptedException => () }
|
||||
|
||||
private[this] def appendLine(buffer: Appendable): String => Unit =
|
||||
line =>
|
||||
{
|
||||
buffer.append(line)
|
||||
buffer.append(Newline)
|
||||
}
|
||||
|
||||
private[this] def transferFullyImpl(in: InputStream, out: OutputStream): Unit = {
|
||||
val continueCount = 1 //if(in.isInstanceOf[PipedInputStream]) 1 else 0
|
||||
val buffer = new Array[Byte](BufferSize)
|
||||
def read(): Unit = {
|
||||
val byteCount = in.read(buffer)
|
||||
if (byteCount >= continueCount) {
|
||||
out.write(buffer, 0, byteCount)
|
||||
out.flush()
|
||||
read
|
||||
}
|
||||
}
|
||||
read
|
||||
in.close()
|
||||
}
|
||||
|
||||
def inheritInput(connect: Boolean) = { p: JProcessBuilder => if (connect) InheritInput(p) else false }
|
||||
}
|
||||
private[sbt] object ExitCodes {
|
||||
def ignoreFirst: (Int, Int) => Int = (a, b) => b
|
||||
def firstIfNonzero: (Int, Int) => Int = (a, b) => if (a != 0) a else b
|
||||
}
|
||||
|
||||
private abstract class AbstractProcessBuilder extends ProcessBuilder with SinkPartialBuilder with SourcePartialBuilder {
|
||||
def #&&(other: ProcessBuilder): ProcessBuilder = new AndProcessBuilder(this, other)
|
||||
def #||(other: ProcessBuilder): ProcessBuilder = new OrProcessBuilder(this, other)
|
||||
def #|(other: ProcessBuilder): ProcessBuilder =
|
||||
{
|
||||
require(other.canPipeTo, "Piping to multiple processes is not supported.")
|
||||
new PipedProcessBuilder(this, other, false, exitCode = ExitCodes.ignoreFirst)
|
||||
}
|
||||
def ###(other: ProcessBuilder): ProcessBuilder = new SequenceProcessBuilder(this, other)
|
||||
|
||||
protected def toSource = this
|
||||
protected def toSink = this
|
||||
|
||||
def run(): Process = run(false)
|
||||
def run(connectInput: Boolean): Process = run(BasicIO.standard(connectInput))
|
||||
def run(log: ProcessLogger): Process = run(log, false)
|
||||
def run(log: ProcessLogger, connectInput: Boolean): Process = run(BasicIO(log, connectInput))
|
||||
|
||||
private[this] def getString(log: Option[ProcessLogger], withIn: Boolean): String =
|
||||
{
|
||||
val buffer = new StringBuffer
|
||||
val code = this ! BasicIO(buffer, log, withIn)
|
||||
if (code == 0) buffer.toString else sys.error("Nonzero exit value: " + code)
|
||||
}
|
||||
def !! = getString(None, false)
|
||||
def !!(log: ProcessLogger) = getString(Some(log), false)
|
||||
def !!< = getString(None, true)
|
||||
def !!<(log: ProcessLogger) = getString(Some(log), true)
|
||||
|
||||
def lines: Stream[String] = lines(false, true, None)
|
||||
def lines(log: ProcessLogger): Stream[String] = lines(false, true, Some(log))
|
||||
def lines_! : Stream[String] = lines(false, false, None)
|
||||
def lines_!(log: ProcessLogger): Stream[String] = lines(false, false, Some(log))
|
||||
|
||||
private[this] def lines(withInput: Boolean, nonZeroException: Boolean, log: Option[ProcessLogger]): Stream[String] =
|
||||
{
|
||||
val streamed = Streamed[String](nonZeroException)
|
||||
val process = run(new ProcessIO(BasicIO.input(withInput), BasicIO.processFully(streamed.process), BasicIO.getErr(log), BasicIO.inheritInput(withInput)))
|
||||
Spawn { streamed.done(process.exitValue()) }
|
||||
streamed.stream()
|
||||
}
|
||||
|
||||
def ! = run(false).exitValue()
|
||||
def !< = run(true).exitValue()
|
||||
def !(log: ProcessLogger) = runBuffered(log, false).exitValue()
|
||||
def !<(log: ProcessLogger) = runBuffered(log, true).exitValue()
|
||||
def runBuffered(log: ProcessLogger, connectInput: Boolean) =
|
||||
log.buffer { run(log, connectInput) }
|
||||
def !(io: ProcessIO) = run(io).exitValue()
|
||||
|
||||
def canPipeTo = false
|
||||
}
|
||||
|
||||
private[sbt] class URLBuilder(url: URL) extends URLPartialBuilder with SourcePartialBuilder {
|
||||
protected def toSource = new URLInput(url)
|
||||
}
|
||||
private[sbt] class FileBuilder(base: File) extends FilePartialBuilder with SinkPartialBuilder with SourcePartialBuilder {
|
||||
protected def toSource = new FileInput(base)
|
||||
protected def toSink = new FileOutput(base, false)
|
||||
def #<<(f: File): ProcessBuilder = #<<(new FileInput(f))
|
||||
def #<<(u: URL): ProcessBuilder = #<<(new URLInput(u))
|
||||
def #<<(s: => InputStream): ProcessBuilder = #<<(new InputStreamBuilder(s))
|
||||
def #<<(b: ProcessBuilder): ProcessBuilder = new PipedProcessBuilder(b, new FileOutput(base, true), false, ExitCodes.firstIfNonzero)
|
||||
}
|
||||
|
||||
private abstract class BasicBuilder extends AbstractProcessBuilder {
|
||||
protected[this] def checkNotThis(a: ProcessBuilder) = require(a != this, "Compound process '" + a + "' cannot contain itself.")
|
||||
final def run(io: ProcessIO): Process =
|
||||
{
|
||||
val p = createProcess(io)
|
||||
p.start()
|
||||
p
|
||||
}
|
||||
protected[this] def createProcess(io: ProcessIO): BasicProcess
|
||||
}
|
||||
private abstract class BasicProcess extends Process {
|
||||
def start(): Unit
|
||||
}
|
||||
|
||||
private abstract class CompoundProcess extends BasicProcess {
|
||||
def destroy(): Unit = destroyer()
|
||||
def exitValue() = getExitValue().getOrElse(sys.error("No exit code: process destroyed."))
|
||||
|
||||
def start() = getExitValue
|
||||
|
||||
protected lazy val (getExitValue, destroyer) =
|
||||
{
|
||||
val code = new SyncVar[Option[Int]]()
|
||||
code.set(None)
|
||||
val thread = Spawn(code.set(runAndExitValue()))
|
||||
|
||||
(
|
||||
Future { thread.join(); code.get },
|
||||
() => thread.interrupt()
|
||||
)
|
||||
}
|
||||
|
||||
/** Start and block until the exit value is available and then return it in Some. Return None if destroyed (use 'run')*/
|
||||
protected[this] def runAndExitValue(): Option[Int]
|
||||
|
||||
protected[this] def runInterruptible[T](action: => T)(destroyImpl: => Unit): Option[T] =
|
||||
{
|
||||
try { Some(action) }
|
||||
catch { case _: InterruptedException => destroyImpl; None }
|
||||
}
|
||||
}
|
||||
|
||||
private abstract class SequentialProcessBuilder(a: ProcessBuilder, b: ProcessBuilder, operatorString: String) extends BasicBuilder {
|
||||
checkNotThis(a)
|
||||
checkNotThis(b)
|
||||
override def toString = " ( " + a + " " + operatorString + " " + b + " ) "
|
||||
}
|
||||
private class PipedProcessBuilder(first: ProcessBuilder, second: ProcessBuilder, toError: Boolean, exitCode: (Int, Int) => Int) extends SequentialProcessBuilder(first, second, if (toError) "#|!" else "#|") {
|
||||
override def createProcess(io: ProcessIO) = new PipedProcesses(first, second, io, toError, exitCode)
|
||||
}
|
||||
private class AndProcessBuilder(first: ProcessBuilder, second: ProcessBuilder) extends SequentialProcessBuilder(first, second, "#&&") {
|
||||
override def createProcess(io: ProcessIO) = new AndProcess(first, second, io)
|
||||
}
|
||||
private class OrProcessBuilder(first: ProcessBuilder, second: ProcessBuilder) extends SequentialProcessBuilder(first, second, "#||") {
|
||||
override def createProcess(io: ProcessIO) = new OrProcess(first, second, io)
|
||||
}
|
||||
private class SequenceProcessBuilder(first: ProcessBuilder, second: ProcessBuilder) extends SequentialProcessBuilder(first, second, "###") {
|
||||
override def createProcess(io: ProcessIO) = new ProcessSequence(first, second, io)
|
||||
}
|
||||
|
||||
private class SequentialProcess(a: ProcessBuilder, b: ProcessBuilder, io: ProcessIO, evaluateSecondProcess: Int => Boolean) extends CompoundProcess {
|
||||
protected[this] override def runAndExitValue() =
|
||||
{
|
||||
val first = a.run(io)
|
||||
runInterruptible(first.exitValue)(first.destroy()) flatMap
|
||||
{ codeA =>
|
||||
if (evaluateSecondProcess(codeA)) {
|
||||
val second = b.run(io)
|
||||
runInterruptible(second.exitValue)(second.destroy())
|
||||
} else
|
||||
Some(codeA)
|
||||
}
|
||||
}
|
||||
}
|
||||
private class AndProcess(a: ProcessBuilder, b: ProcessBuilder, io: ProcessIO) extends SequentialProcess(a, b, io, _ == 0)
|
||||
private class OrProcess(a: ProcessBuilder, b: ProcessBuilder, io: ProcessIO) extends SequentialProcess(a, b, io, _ != 0)
|
||||
private class ProcessSequence(a: ProcessBuilder, b: ProcessBuilder, io: ProcessIO) extends SequentialProcess(a, b, io, ignore => true)
|
||||
|
||||
private class PipedProcesses(a: ProcessBuilder, b: ProcessBuilder, defaultIO: ProcessIO, toError: Boolean, exitCode: (Int, Int) => Int) extends CompoundProcess {
|
||||
protected[this] override def runAndExitValue() =
|
||||
{
|
||||
val currentSource = new SyncVar[Option[InputStream]]
|
||||
val pipeOut = new PipedOutputStream
|
||||
val source = new PipeSource(currentSource, pipeOut, a.toString)
|
||||
source.start()
|
||||
|
||||
val pipeIn = new PipedInputStream(pipeOut)
|
||||
val currentSink = new SyncVar[Option[OutputStream]]
|
||||
val sink = new PipeSink(pipeIn, currentSink, b.toString)
|
||||
sink.start()
|
||||
|
||||
def handleOutOrError(fromOutput: InputStream) = currentSource.put(Some(fromOutput))
|
||||
|
||||
val firstIO =
|
||||
if (toError)
|
||||
defaultIO.withError(handleOutOrError)
|
||||
else
|
||||
defaultIO.withOutput(handleOutOrError)
|
||||
val secondIO = defaultIO.withInput(toInput => currentSink.put(Some(toInput)))
|
||||
|
||||
val second = b.run(secondIO)
|
||||
val first = a.run(firstIO)
|
||||
try {
|
||||
runInterruptible {
|
||||
val firstResult = first.exitValue
|
||||
currentSource.put(None)
|
||||
currentSink.put(None)
|
||||
val secondResult = second.exitValue
|
||||
exitCode(firstResult, secondResult)
|
||||
} {
|
||||
first.destroy()
|
||||
second.destroy()
|
||||
}
|
||||
} finally {
|
||||
BasicIO.close(pipeIn)
|
||||
BasicIO.close(pipeOut)
|
||||
}
|
||||
}
|
||||
}
|
||||
private class PipeSource(currentSource: SyncVar[Option[InputStream]], pipe: PipedOutputStream, label: => String) extends Thread {
|
||||
final override def run(): Unit = {
|
||||
currentSource.get match {
|
||||
case Some(source) =>
|
||||
try { BasicIO.transferFully(source, pipe) }
|
||||
catch { case e: IOException => println("I/O error " + e.getMessage + " for process: " + label); e.printStackTrace() }
|
||||
finally {
|
||||
BasicIO.close(source)
|
||||
currentSource.unset()
|
||||
}
|
||||
run()
|
||||
case None =>
|
||||
currentSource.unset()
|
||||
BasicIO.close(pipe)
|
||||
}
|
||||
}
|
||||
}
|
||||
private class PipeSink(pipe: PipedInputStream, currentSink: SyncVar[Option[OutputStream]], label: => String) extends Thread {
|
||||
final override def run(): Unit = {
|
||||
currentSink.get match {
|
||||
case Some(sink) =>
|
||||
try { BasicIO.transferFully(pipe, sink) }
|
||||
catch { case e: IOException => println("I/O error " + e.getMessage + " for process: " + label); e.printStackTrace() }
|
||||
finally {
|
||||
BasicIO.close(sink)
|
||||
currentSink.unset()
|
||||
}
|
||||
run()
|
||||
case None =>
|
||||
currentSink.unset()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private[sbt] class DummyProcessBuilder(override val toString: String, exitValue: => Int) extends AbstractProcessBuilder {
|
||||
override def run(io: ProcessIO): Process = new DummyProcess(exitValue)
|
||||
override def canPipeTo = true
|
||||
}
|
||||
/**
|
||||
* A thin wrapper around a java.lang.Process. `ioThreads` are the Threads created to do I/O.
|
||||
* The implementation of `exitValue` waits until these threads die before returning.
|
||||
*/
|
||||
private class DummyProcess(action: => Int) extends Process {
|
||||
private[this] val exitCode = Future(action)
|
||||
override def exitValue() = exitCode()
|
||||
override def destroy(): Unit = ()
|
||||
}
|
||||
/** Represents a simple command without any redirection or combination. */
|
||||
private[sbt] class SimpleProcessBuilder(p: JProcessBuilder) extends AbstractProcessBuilder {
|
||||
override def run(io: ProcessIO): Process =
|
||||
{
|
||||
import io._
|
||||
val inherited = inheritInput(p)
|
||||
val process = p.start()
|
||||
|
||||
// spawn threads that process the output and error streams, and also write input if not inherited.
|
||||
if (!inherited)
|
||||
Spawn(writeInput(process.getOutputStream))
|
||||
val outThread = Spawn(processOutput(process.getInputStream))
|
||||
val errorThread =
|
||||
if (!p.redirectErrorStream)
|
||||
Spawn(processError(process.getErrorStream)) :: Nil
|
||||
else
|
||||
Nil
|
||||
new SimpleProcess(process, outThread :: errorThread)
|
||||
}
|
||||
override def toString = p.command.toString
|
||||
override def canPipeTo = true
|
||||
}
|
||||
|
||||
/**
|
||||
* A thin wrapper around a java.lang.Process. `outputThreads` are the Threads created to read from the
|
||||
* output and error streams of the process.
|
||||
* The implementation of `exitValue` wait for the process to finish and then waits until the threads reading output and error streams die before
|
||||
* returning. Note that the thread that reads the input stream cannot be interrupted, see https://github.com/sbt/sbt/issues/327 and
|
||||
* http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4514257
|
||||
*/
|
||||
private class SimpleProcess(p: JProcess, outputThreads: List[Thread]) extends Process {
|
||||
override def exitValue() =
|
||||
{
|
||||
try {
|
||||
p.waitFor()
|
||||
} catch {
|
||||
case _: InterruptedException => p.destroy()
|
||||
}
|
||||
outputThreads.foreach(_.join()) // this ensures that all output is complete before returning (waitFor does not ensure this)
|
||||
p.exitValue()
|
||||
}
|
||||
override def destroy() = p.destroy()
|
||||
}
|
||||
|
||||
private class FileOutput(file: File, append: Boolean) extends OutputStreamBuilder(new FileOutputStream(file, append), file.getAbsolutePath)
|
||||
private class URLInput(url: URL) extends InputStreamBuilder(url.openStream, url.toString)
|
||||
private class FileInput(file: File) extends InputStreamBuilder(new FileInputStream(file), file.getAbsolutePath)
|
||||
|
||||
import Uncloseable.protect
|
||||
private class OutputStreamBuilder(stream: => OutputStream, label: String) extends ThreadProcessBuilder(label, _.writeInput(protect(stream))) {
|
||||
def this(stream: => OutputStream) = this(stream, "<output stream>")
|
||||
}
|
||||
private class InputStreamBuilder(stream: => InputStream, label: String) extends ThreadProcessBuilder(label, _.processOutput(protect(stream))) {
|
||||
def this(stream: => InputStream) = this(stream, "<input stream>")
|
||||
}
|
||||
|
||||
private abstract class ThreadProcessBuilder(override val toString: String, runImpl: ProcessIO => Unit) extends AbstractProcessBuilder {
|
||||
override def run(io: ProcessIO): Process =
|
||||
{
|
||||
val success = new SyncVar[Boolean]
|
||||
success.put(false)
|
||||
new ThreadProcess(Spawn { runImpl(io); success.set(true) }, success)
|
||||
}
|
||||
}
|
||||
private final class ThreadProcess(thread: Thread, success: SyncVar[Boolean]) extends Process {
|
||||
override def exitValue() =
|
||||
{
|
||||
thread.join()
|
||||
if (success.get) 0 else 1
|
||||
}
|
||||
override def destroy(): Unit = thread.interrupt()
|
||||
}
|
||||
|
||||
object Uncloseable {
|
||||
def apply(in: InputStream): InputStream = new FilterInputStream(in) { override def close(): Unit = () }
|
||||
def apply(out: OutputStream): OutputStream = new FilterOutputStream(out) { override def close(): Unit = () }
|
||||
def protect(in: InputStream): InputStream = if (in eq System.in) Uncloseable(in) else in
|
||||
def protect(out: OutputStream): OutputStream = if ((out eq System.out) || (out eq System.err)) Uncloseable(out) else out
|
||||
}
|
||||
private object Streamed {
|
||||
def apply[T](nonzeroException: Boolean): Streamed[T] =
|
||||
{
|
||||
val q = new java.util.concurrent.LinkedBlockingQueue[Either[Int, T]]
|
||||
def next(): Stream[T] =
|
||||
q.take match {
|
||||
case Left(0) => Stream.empty
|
||||
case Left(code) => if (nonzeroException) sys.error("Nonzero exit code: " + code) else Stream.empty
|
||||
case Right(s) => Stream.cons(s, next)
|
||||
}
|
||||
new Streamed((s: T) => q.put(Right(s)), code => q.put(Left(code)), () => next())
|
||||
}
|
||||
}
|
||||
|
||||
private final class Streamed[T](val process: T => Unit, val done: Int => Unit, val stream: () => Stream[T]) extends NotNull
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
package sbt
|
||||
|
||||
// minimal copy of scala.concurrent.SyncVar since that version deprecated put and unset
|
||||
private[sbt] final class SyncVar[A] {
|
||||
private[this] var isDefined: Boolean = false
|
||||
private[this] var value: Option[A] = None
|
||||
|
||||
/** Waits until a value is set and then gets it. Does not clear the value */
|
||||
def get: A = synchronized {
|
||||
while (!isDefined) wait()
|
||||
value.get
|
||||
}
|
||||
|
||||
/** Waits until a value is set, gets it, and finally clears the value. */
|
||||
def take(): A = synchronized {
|
||||
try get finally unset()
|
||||
}
|
||||
|
||||
/** Sets the value, whether or not it is currently defined. */
|
||||
def set(x: A): Unit = synchronized {
|
||||
isDefined = true
|
||||
value = Some(x)
|
||||
notifyAll()
|
||||
}
|
||||
|
||||
/** Sets the value, first waiting until it is undefined if it is currently defined. */
|
||||
def put(x: A): Unit = synchronized {
|
||||
while (isDefined) wait()
|
||||
set(x)
|
||||
}
|
||||
|
||||
/** Clears the value, whether or not it is current defined. */
|
||||
def unset(): Unit = synchronized {
|
||||
isDefined = false
|
||||
value = None
|
||||
notifyAll()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1,131 +0,0 @@
|
|||
package sbt
|
||||
|
||||
import java.io.File
|
||||
import org.scalacheck.{ Arbitrary, Gen, Prop, Properties }
|
||||
import Prop._
|
||||
|
||||
import Process._
|
||||
|
||||
object ProcessSpecification extends Properties("Process I/O") {
|
||||
implicit val exitCodeArb: Arbitrary[Array[Byte]] = Arbitrary(
|
||||
for (
|
||||
size <- Gen.choose(0, 10);
|
||||
l <- Gen.listOfN[Byte](size, Arbitrary.arbByte.arbitrary)
|
||||
) yield l.toArray
|
||||
)
|
||||
|
||||
/*property("Correct exit code") = forAll( (exitCode: Byte) => checkExit(exitCode))
|
||||
property("#&& correct") = forAll( (exitCodes: Array[Byte]) => checkBinary(exitCodes)(_ #&& _)(_ && _))
|
||||
property("#|| correct") = forAll( (exitCodes: Array[Byte]) => checkBinary(exitCodes)(_ #|| _)(_ || _))
|
||||
property("### correct") = forAll( (exitCodes: Array[Byte]) => checkBinary(exitCodes)(_ ### _)( (x,latest) => latest))*/
|
||||
property("Pipe to output file") = forAll((data: Array[Byte]) => checkFileOut(data))
|
||||
property("Pipe from input file") = forAll((data: Array[Byte]) => checkFileIn(data))
|
||||
property("Pipe to process") = forAll((data: Array[Byte]) => checkPipe(data))
|
||||
property("Pipe to process ignores input exit code") = forAll((data: Array[Byte], code: Byte) => checkPipeExit(data, code))
|
||||
property("Pipe from input file to bad process preserves correct exit code.") = forAll((data: Array[Byte], code: Byte) => checkFileInExit(data, code))
|
||||
property("Pipe to output file from bad process preserves correct exit code.") = forAll((data: Array[Byte], code: Byte) => checkFileOutExit(data, code))
|
||||
|
||||
private def checkBinary(codes: Array[Byte])(reduceProcesses: (ProcessBuilder, ProcessBuilder) => ProcessBuilder)(reduceExit: (Boolean, Boolean) => Boolean) =
|
||||
{
|
||||
(codes.length > 1) ==>
|
||||
{
|
||||
val unsignedCodes = codes.map(unsigned)
|
||||
val exitCode = unsignedCodes.map(code => Process(process("sbt.exit " + code))).reduceLeft(reduceProcesses) !
|
||||
val expectedExitCode = unsignedCodes.map(toBoolean).reduceLeft(reduceExit)
|
||||
toBoolean(exitCode) == expectedExitCode
|
||||
}
|
||||
}
|
||||
private def toBoolean(exitCode: Int) = exitCode == 0
|
||||
private def checkExit(code: Byte) =
|
||||
{
|
||||
val exitCode = unsigned(code)
|
||||
(process("sbt.exit " + exitCode) !) == exitCode
|
||||
}
|
||||
private def checkFileOut(data: Array[Byte]) =
|
||||
{
|
||||
withData(data) { (temporaryFile, temporaryFile2) =>
|
||||
val catCommand = process("sbt.cat " + temporaryFile.getAbsolutePath)
|
||||
catCommand #> temporaryFile2
|
||||
}
|
||||
}
|
||||
private def checkFileIn(data: Array[Byte]) =
|
||||
{
|
||||
withData(data) { (temporaryFile, temporaryFile2) =>
|
||||
val catCommand = process("sbt.cat")
|
||||
temporaryFile #> catCommand #> temporaryFile2
|
||||
}
|
||||
}
|
||||
private def checkPipe(data: Array[Byte]) =
|
||||
{
|
||||
withData(data) { (temporaryFile, temporaryFile2) =>
|
||||
val catCommand = process("sbt.cat")
|
||||
temporaryFile #> catCommand #| catCommand #> temporaryFile2
|
||||
}
|
||||
}
|
||||
private def checkPipeExit(data: Array[Byte], code: Byte) =
|
||||
withTempFiles { (a, b) =>
|
||||
IO.write(a, data)
|
||||
val catCommand = process("sbt.cat")
|
||||
val exitCommand = process(s"sbt.exit $code")
|
||||
val exit = (a #> exitCommand #| catCommand #> b).!
|
||||
(s"Exit code: $exit") |:
|
||||
(s"Output file length: ${b.length}") |:
|
||||
(exit == 0) &&
|
||||
(b.length == 0)
|
||||
}
|
||||
|
||||
private def checkFileOutExit(data: Array[Byte], exitCode: Byte) =
|
||||
withTempFiles { (a, b) =>
|
||||
IO.write(a, data)
|
||||
val code = unsigned(exitCode)
|
||||
val command = process(s"sbt.exit $code")
|
||||
val exit = (a #> command #> b).!
|
||||
(s"Exit code: $exit, expected: $code") |:
|
||||
(s"Output file length: ${b.length}") |:
|
||||
(exit == code) &&
|
||||
(b.length == 0)
|
||||
}
|
||||
|
||||
private def checkFileInExit(data: Array[Byte], exitCode: Byte) =
|
||||
withTempFiles { (a, b) =>
|
||||
IO.write(a, data)
|
||||
val code = unsigned(exitCode)
|
||||
val command = process(s"sbt.exit $code")
|
||||
val exit = (a #> command).!
|
||||
(s"Exit code: $exit, expected: $code") |:
|
||||
(exit == code)
|
||||
}
|
||||
|
||||
private def temp() = File.createTempFile("sbt", "")
|
||||
private def withData(data: Array[Byte])(f: (File, File) => ProcessBuilder) =
|
||||
withTempFiles { (a, b) =>
|
||||
IO.write(a, data)
|
||||
val process = f(a, b)
|
||||
(process !) == 0 && sameFiles(a, b)
|
||||
}
|
||||
private def sameFiles(a: File, b: File) =
|
||||
IO.readBytes(a) sameElements IO.readBytes(b)
|
||||
|
||||
private def withTempFiles[T](f: (File, File) => T): T =
|
||||
{
|
||||
val temporaryFile1 = temp()
|
||||
val temporaryFile2 = temp()
|
||||
try f(temporaryFile1, temporaryFile2)
|
||||
finally {
|
||||
temporaryFile1.delete()
|
||||
temporaryFile2.delete()
|
||||
}
|
||||
}
|
||||
private def unsigned(b: Int): Int = ((b: Int) + 256) % 256
|
||||
private def unsigned(b: Byte): Int = unsigned(b: Int)
|
||||
private def process(command: String) =
|
||||
{
|
||||
val ignore = echo // just for the compile dependency so that this test is rerun when TestedProcess.scala changes, not used otherwise
|
||||
|
||||
val thisClasspath = List(getSource[Product], getSource[IO.type], getSource[SourceTag]).mkString(File.pathSeparator)
|
||||
"java -cp " + thisClasspath + " " + command
|
||||
}
|
||||
private def getSource[T: Manifest]: String =
|
||||
IO.classLocationFile[T].getAbsolutePath
|
||||
}
|
||||
private trait SourceTag
|
||||
|
|
@ -1,46 +0,0 @@
|
|||
package sbt
|
||||
|
||||
import java.io.{ File, FileNotFoundException, IOException }
|
||||
|
||||
object exit {
|
||||
def main(args: Array[String]): Unit = {
|
||||
System.exit(java.lang.Integer.parseInt(args(0)))
|
||||
}
|
||||
}
|
||||
object cat {
|
||||
def main(args: Array[String]): Unit = {
|
||||
try {
|
||||
if (args.length == 0)
|
||||
IO.transfer(System.in, System.out)
|
||||
else
|
||||
catFiles(args.toList)
|
||||
System.exit(0)
|
||||
} catch {
|
||||
case e: Throwable =>
|
||||
e.printStackTrace()
|
||||
System.err.println("Error: " + e.toString)
|
||||
System.exit(1)
|
||||
}
|
||||
}
|
||||
private def catFiles(filenames: List[String]): Option[String] =
|
||||
{
|
||||
filenames match {
|
||||
case head :: tail =>
|
||||
val file = new File(head)
|
||||
if (file.isDirectory)
|
||||
throw new IOException("Is directory: " + file)
|
||||
else if (file.exists) {
|
||||
Using.fileInputStream(file) { stream =>
|
||||
IO.transfer(stream, System.out)
|
||||
}
|
||||
catFiles(tail)
|
||||
} else
|
||||
throw new FileNotFoundException("No such file or directory: " + file)
|
||||
case Nil => None
|
||||
}
|
||||
}
|
||||
}
|
||||
object echo {
|
||||
def main(args: Array[String]): Unit =
|
||||
System.out.println(args.mkString(" "))
|
||||
}
|
||||
Loading…
Reference in New Issue