From 4629053277afb703829ca2b586d4b4ed12e35e17 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Thu, 20 Aug 2015 01:00:26 -0400 Subject: [PATCH] remove process --- internal/process/NOTICE | 3 - .../src/main/scala/sbt/InheritInput.scala | 21 - .../process/src/main/scala/sbt/Process.scala | 221 --------- .../src/main/scala/sbt/ProcessImpl.scala | 436 ------------------ .../process/src/main/scala/sbt/SyncVar.scala | 39 -- .../src/test/scala/ProcessSpecification.scala | 131 ------ .../src/test/scala/TestedProcess.scala | 46 -- 7 files changed, 897 deletions(-) delete mode 100644 internal/process/NOTICE delete mode 100755 internal/process/src/main/scala/sbt/InheritInput.scala delete mode 100644 internal/process/src/main/scala/sbt/Process.scala delete mode 100644 internal/process/src/main/scala/sbt/ProcessImpl.scala delete mode 100644 internal/process/src/main/scala/sbt/SyncVar.scala delete mode 100644 internal/process/src/test/scala/ProcessSpecification.scala delete mode 100644 internal/process/src/test/scala/TestedProcess.scala diff --git a/internal/process/NOTICE b/internal/process/NOTICE deleted file mode 100644 index 789c9ff1f..000000000 --- a/internal/process/NOTICE +++ /dev/null @@ -1,3 +0,0 @@ -Simple Build Tool: Process Component -Copyright 2008, 2009, 2010 Mark Harrah, Vesa Vilhonen -Licensed under BSD-style license (see LICENSE) \ No newline at end of file diff --git a/internal/process/src/main/scala/sbt/InheritInput.scala b/internal/process/src/main/scala/sbt/InheritInput.scala deleted file mode 100755 index a9828b04d..000000000 --- a/internal/process/src/main/scala/sbt/InheritInput.scala +++ /dev/null @@ -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) -} diff --git a/internal/process/src/main/scala/sbt/Process.scala b/internal/process/src/main/scala/sbt/Process.scala deleted file mode 100644 index 79435367d..000000000 --- a/internal/process/src/main/scala/sbt/Process.scala +++ /dev/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 -} diff --git a/internal/process/src/main/scala/sbt/ProcessImpl.scala b/internal/process/src/main/scala/sbt/ProcessImpl.scala deleted file mode 100644 index abef81b33..000000000 --- a/internal/process/src/main/scala/sbt/ProcessImpl.scala +++ /dev/null @@ -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, "") -} -private class InputStreamBuilder(stream: => InputStream, label: String) extends ThreadProcessBuilder(label, _.processOutput(protect(stream))) { - def this(stream: => InputStream) = this(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 diff --git a/internal/process/src/main/scala/sbt/SyncVar.scala b/internal/process/src/main/scala/sbt/SyncVar.scala deleted file mode 100644 index c268aac3d..000000000 --- a/internal/process/src/main/scala/sbt/SyncVar.scala +++ /dev/null @@ -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() - } -} - diff --git a/internal/process/src/test/scala/ProcessSpecification.scala b/internal/process/src/test/scala/ProcessSpecification.scala deleted file mode 100644 index 67bd5e625..000000000 --- a/internal/process/src/test/scala/ProcessSpecification.scala +++ /dev/null @@ -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 diff --git a/internal/process/src/test/scala/TestedProcess.scala b/internal/process/src/test/scala/TestedProcess.scala deleted file mode 100644 index 8dabcf381..000000000 --- a/internal/process/src/test/scala/TestedProcess.scala +++ /dev/null @@ -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(" ")) -}