mirror of https://github.com/sbt/sbt.git
* Worked around Java issue converting URL to File
* Processes can now redirect standard input (see run with Boolean argument or !< operator on ProcessBuilder), off by default * Terminal echo reenabled when JLine is not reading in a line * Properly detect unsupported terminals using JLine's Terminal.isANSISupported method git-svn-id: https://simple-build-tool.googlecode.com/svn/trunk@853 d89573ee-9141-11dd-94d4-bdf5e562f29c
This commit is contained in:
parent
8090c63519
commit
c350ab6b3c
|
|
@ -52,7 +52,7 @@ private[sbt] object ClasspathUtilities
|
|||
{
|
||||
url.getProtocol match
|
||||
{
|
||||
case "file" => new File(url.toURI) :: Nil
|
||||
case "file" => FileUtilities.toFile(url) :: Nil
|
||||
case "jar" =>
|
||||
val path = url.getPath
|
||||
val end = path.indexOf('!')
|
||||
|
|
|
|||
|
|
@ -300,8 +300,8 @@ abstract class BasicScalaProject extends ScalaProject with BasicDependencyProjec
|
|||
override def deliverScalaDependencies: Iterable[ModuleID] =
|
||||
{
|
||||
val snapshot = mainDependencies.snapshot
|
||||
mapScalaModule(snapshot.scalaLibrary, ManageDependencies.ScalaLibraryID) ++
|
||||
mapScalaModule(snapshot.scalaCompiler, ManageDependencies.ScalaCompilerID)
|
||||
mapScalaModule(snapshot.scalaLibrary, ScalaArtifacts.LibraryID) ++
|
||||
mapScalaModule(snapshot.scalaCompiler, ScalaArtifacts.CompilerID)
|
||||
}
|
||||
override def watchPaths = mainSources +++ testSources +++ mainResources +++ testResources
|
||||
}
|
||||
|
|
@ -413,7 +413,7 @@ object BasicScalaProject
|
|||
private def mapScalaModule(in: Iterable[_], id: String) =
|
||||
{
|
||||
ScalaVersion.current.toList.flatMap { scalaVersion =>
|
||||
in.map(jar => ModuleID(ManageDependencies.ScalaOrganization, id, scalaVersion))
|
||||
in.map(jar => ModuleID(ScalaArtifacts.Organization, id, scalaVersion))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -467,8 +467,8 @@ final class LibraryDependencies(project: Project, conditional: CompileConditiona
|
|||
}
|
||||
private object LibraryDependencies
|
||||
{
|
||||
private def ScalaLibraryPrefix = ManageDependencies.ScalaLibraryID
|
||||
private def ScalaCompilerPrefix = ManageDependencies.ScalaCompilerID
|
||||
private def ScalaLibraryPrefix = ScalaArtifacts.LibraryID
|
||||
private def ScalaCompilerPrefix = ScalaArtifacts.CompilerID
|
||||
private def ScalaJarPrefixes = List(ScalaCompilerPrefix, ScalaLibraryPrefix)
|
||||
private def isScalaJar(file: File) = ClasspathUtilities.isArchive(file) && ScalaJarPrefixes.exists(isNamed(file))
|
||||
private def isScalaLibraryJar(file: File) = isNamed(file)(ScalaLibraryPrefix)
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import java.io.{Closeable, File, FileInputStream, FileOutputStream, InputStream,
|
|||
import java.io.{ByteArrayOutputStream, InputStreamReader, OutputStreamWriter}
|
||||
import java.io.{BufferedReader, BufferedWriter, FileReader, FileWriter, Reader, Writer}
|
||||
import java.util.zip.{GZIPInputStream, GZIPOutputStream}
|
||||
import java.net.URL
|
||||
import java.net.{URL, URISyntaxException}
|
||||
import java.nio.charset.{Charset, CharsetDecoder, CharsetEncoder}
|
||||
import java.nio.channels.FileChannel
|
||||
import java.util.jar.{Attributes, JarEntry, JarFile, JarInputStream, JarOutputStream, Manifest}
|
||||
|
|
@ -788,10 +788,14 @@ object FileUtilities
|
|||
writer.write(Newline)
|
||||
}
|
||||
|
||||
def toFile(url: URL) =
|
||||
try { new File(url.toURI) }
|
||||
catch { case _: URISyntaxException => new File(url.getPath) }
|
||||
|
||||
/** The directory in which temporary files are placed.*/
|
||||
val temporaryDirectory = new File(System.getProperty("java.io.tmpdir"))
|
||||
def classLocation(cl: Class[_]): URL = cl.getProtectionDomain.getCodeSource.getLocation
|
||||
def classLocationFile(cl: Class[_]): File = new File(classLocation(cl).toURI)
|
||||
def classLocationFile(cl: Class[_]): File = toFile(classLocation(cl))
|
||||
def classLocation[T](implicit mf: scala.reflect.Manifest[T]): URL = classLocation(mf.erasure)
|
||||
def classLocationFile[T](implicit mf: scala.reflect.Manifest[T]): File = classLocationFile(mf.erasure)
|
||||
|
||||
|
|
|
|||
|
|
@ -14,21 +14,39 @@ import jline.ConsoleReader
|
|||
abstract class JLine extends LineReader
|
||||
{
|
||||
protected[this] val reader: ConsoleReader
|
||||
def readLine(prompt: String) =
|
||||
def readLine(prompt: String) = JLine.withJLine { unsynchronizedReadLine(prompt) }
|
||||
private[this] def unsynchronizedReadLine(prompt: String) =
|
||||
reader.readLine(prompt) match
|
||||
{
|
||||
case null => None
|
||||
case x => Some(x.trim)
|
||||
}
|
||||
}
|
||||
object SimpleReader extends JLine
|
||||
private object JLine
|
||||
{
|
||||
protected[this] val reader =
|
||||
def terminal = jline.Terminal.getTerminal
|
||||
def createReader() =
|
||||
terminal.synchronized
|
||||
{
|
||||
val cr = new ConsoleReader
|
||||
terminal.enableEcho()
|
||||
cr.setBellEnabled(false)
|
||||
cr
|
||||
}
|
||||
def withJLine[T](action: => T): T =
|
||||
{
|
||||
val t = terminal
|
||||
t.synchronized
|
||||
{
|
||||
t.disableEcho()
|
||||
try { action }
|
||||
finally { t.enableEcho() }
|
||||
}
|
||||
}
|
||||
}
|
||||
object SimpleReader extends JLine
|
||||
{
|
||||
protected[this] val reader = JLine.createReader()
|
||||
}
|
||||
class JLineReader(historyPath: Option[Path], completors: Completors, log: Logger) extends JLine
|
||||
{
|
||||
|
|
|
|||
|
|
@ -248,13 +248,12 @@ final class BufferedLogger(delegate: Logger) extends Logger
|
|||
|
||||
object ConsoleLogger
|
||||
{
|
||||
private def os = System.getProperty("os.name")
|
||||
private def isWindows = os.toLowerCase.indexOf("windows") >= 0
|
||||
private def formatExplicitlyDisabled = java.lang.Boolean.getBoolean("sbt.log.noformat")
|
||||
private val formatEnabled = !(formatExplicitlyDisabled || isWindows)
|
||||
private[this] def formatExplicitlyDisabled = java.lang.Boolean.getBoolean("sbt.log.noformat")
|
||||
private[this] def ansiSupported = jline.Terminal.getTerminal.isANSISupported
|
||||
private val formatEnabled = ansiSupported && !formatExplicitlyDisabled
|
||||
}
|
||||
|
||||
/** A logger that logs to the console. On non-windows systems, the level labels are
|
||||
/** A logger that logs to the console. On supported systems, the level labels are
|
||||
* colored.
|
||||
*
|
||||
* This logger is not thread-safe.*/
|
||||
|
|
|
|||
|
|
@ -37,14 +37,17 @@ final class IvyPaths(val projectDirectory: Path, val managedLibDirectory: Path,
|
|||
final class IvyFlags(val validate: Boolean, val addScalaTools: Boolean, val errorIfNoConfiguration: Boolean) extends NotNull
|
||||
final class IvyConfiguration(val paths: IvyPaths, val manager: Manager, val flags: IvyFlags, val ivyScala: Option[IvyScala], val log: Logger) extends NotNull
|
||||
final class UpdateConfiguration(val outputPattern: String, val synchronize: Boolean, val quiet: Boolean) extends NotNull
|
||||
object ScalaArtifacts
|
||||
{
|
||||
val Organization = "org.scala-lang"
|
||||
val LibraryID = "scala-library"
|
||||
val CompilerID = "scala-compiler"
|
||||
}
|
||||
object ManageDependencies
|
||||
{
|
||||
val DefaultIvyConfigFilename = "ivysettings.xml"
|
||||
val DefaultIvyFilename = "ivy.xml"
|
||||
val DefaultMavenFilename = "pom.xml"
|
||||
val ScalaOrganization = "org.scala-lang"
|
||||
val ScalaLibraryID = "scala-library"
|
||||
val ScalaCompilerID = "scala-compiler"
|
||||
|
||||
private def defaultIvyFile(project: Path) = project / DefaultIvyFilename
|
||||
private def defaultIvyConfiguration(project: Path) = project / DefaultIvyConfigFilename
|
||||
|
|
@ -280,7 +283,7 @@ object ManageDependencies
|
|||
Control.lazyFold(module.getDependencies.toList)
|
||||
{ dep =>
|
||||
val id = dep.getDependencyRevisionId
|
||||
if(id.getOrganisation == ScalaOrganization && id.getRevision != scalaVersion && dep.getModuleConfigurations.exists(configSet.contains))
|
||||
if(id.getOrganisation == ScalaArtifacts.Organization && id.getRevision != scalaVersion && dep.getModuleConfigurations.exists(configSet.contains))
|
||||
Some("Different Scala version specified in dependency ("+ id.getRevision + ") than in project (" + scalaVersion + ").")
|
||||
else
|
||||
None
|
||||
|
|
@ -307,9 +310,9 @@ object ManageDependencies
|
|||
}
|
||||
}
|
||||
def excludeScalaJar(name: String)
|
||||
{ module.addExcludeRule(excludeRule(ScalaOrganization, name, configurationNames)) }
|
||||
excludeScalaJar(ScalaLibraryID)
|
||||
excludeScalaJar(ScalaCompilerID)
|
||||
{ module.addExcludeRule(excludeRule(ScalaArtifacts.Organization, name, configurationNames)) }
|
||||
excludeScalaJar(ScalaArtifacts.LibraryID)
|
||||
excludeScalaJar(ScalaArtifacts.CompilerID)
|
||||
}
|
||||
private def configureCache(settings: IvySettings)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -57,12 +57,25 @@ trait ProcessBuilder extends NotNull
|
|||
/** 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 Logger.*/
|
||||
def !(log: Logger): 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 if `connectInput` is true.*/
|
||||
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 Logger. The newly started process reads from standard input of the current process if `connectInput` is true.*/
|
||||
def !<(log: Logger) : 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 Logger.*/
|
||||
def run(log: Logger): 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 Logger.
|
||||
* The newly started process reads from standard input of the current process if `connectInput` is true.*/
|
||||
def run(log: Logger, connectInput: Boolean): Process
|
||||
|
||||
/** Constructs a command that runs this command first and then `other` if this command succeeds.*/
|
||||
def #&& (other: ProcessBuilder): ProcessBuilder
|
||||
|
|
|
|||
|
|
@ -376,7 +376,7 @@ object Project
|
|||
if(builderProjectPath.asFile.isDirectory)
|
||||
{
|
||||
val pluginProjectPath = info.builderPath / PluginProjectDirectoryName
|
||||
val additionalPaths = additional match { case u: URLClassLoader => u.getURLs.map(url => Path.fromFile(new File(url.toURI))); case _ => Nil }
|
||||
val additionalPaths = additional match { case u: URLClassLoader => u.getURLs.map(url => Path.fromFile(FileUtilities.toFile(url))); case _ => Nil }
|
||||
val builderProject = new BuilderProject(ProjectInfo(builderProjectPath.asFile, Nil, None), pluginProjectPath, additionalPaths, buildLog)
|
||||
builderProject.compile.run.toLeft(()).right.flatMap { ignore =>
|
||||
builderProject.projectDefinition.right.map {
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ object Resources
|
|||
throw new Exception("Resource base directory '" + basePath + "' not on classpath.")
|
||||
else
|
||||
{
|
||||
val file = new File(resource.toURI)
|
||||
val file = toFile(resource)
|
||||
if(file.exists)
|
||||
new Resources(file)
|
||||
else
|
||||
|
|
|
|||
|
|
@ -55,11 +55,13 @@ object Run extends ScalaRun
|
|||
log.info("")
|
||||
Control.trapUnit("Error during session: ", log)
|
||||
{
|
||||
JLine.withJLine {
|
||||
val loop = new InterpreterLoop
|
||||
executeTrapExit(loop.main(settings), log)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/** Executes the given function, trapping calls to System.exit. */
|
||||
private def executeTrapExit(f: => Unit, log: Logger): Option[String] =
|
||||
{
|
||||
|
|
@ -128,9 +130,11 @@ object Run extends ScalaRun
|
|||
log.info("")
|
||||
Control.trapUnit("Error during session: ", log)
|
||||
{
|
||||
JLine.withJLine {
|
||||
val loop = new ProjectInterpreterLoop(compilerSettings, project)
|
||||
executeTrapExit(loop.main(interpreterSettings), log)
|
||||
}
|
||||
}
|
||||
}}
|
||||
}
|
||||
/** A custom InterpreterLoop with the purpose of creating an interpreter with Project 'project' bound to the value 'current',
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ trait SimpleScalaProject extends Project
|
|||
{
|
||||
val command = buildCommand
|
||||
log.debug("Executing command " + command)
|
||||
val exitValue = command ! log
|
||||
val exitValue = command.run(log).exitValue() // don't buffer output
|
||||
if(exitValue == 0)
|
||||
None
|
||||
else
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
package sbt
|
||||
|
||||
import java.lang.{Process => JProcess, ProcessBuilder => JProcessBuilder}
|
||||
import java.io.{BufferedReader, Closeable, InputStream, InputStreamReader, IOException, OutputStream}
|
||||
import java.io.{BufferedReader, Closeable, InputStream, InputStreamReader, IOException, OutputStream, PrintStream}
|
||||
import java.io.{PipedInputStream, PipedOutputStream}
|
||||
import java.io.{File, FileInputStream, FileOutputStream}
|
||||
import java.net.URL
|
||||
|
|
@ -25,7 +25,7 @@ private object Spawn
|
|||
|
||||
private object BasicIO
|
||||
{
|
||||
def apply(log: Logger) = new ProcessIO(ignoreOut, processFully(log, Level.Info), processFully(log, Level.Error))
|
||||
def apply(log: Logger, withIn: Boolean) = new ProcessIO(input(withIn), processFully(log, Level.Info), processFully(log, Level.Error))
|
||||
|
||||
def ignoreOut = (i: OutputStream) => ()
|
||||
val BufferSize = 8192
|
||||
|
|
@ -34,9 +34,13 @@ private object BasicIO
|
|||
def processFully(processLine: String => Unit)(i: InputStream)
|
||||
{
|
||||
val reader = new BufferedReader(new InputStreamReader(i))
|
||||
processLinesFully(processLine)(reader.readLine)
|
||||
}
|
||||
def processLinesFully(processLine: String => Unit)(readLine: () => String)
|
||||
{
|
||||
def readFully()
|
||||
{
|
||||
val line = reader.readLine()
|
||||
val line = readLine()
|
||||
if(line != null)
|
||||
{
|
||||
processLine(line)
|
||||
|
|
@ -45,7 +49,16 @@ private object BasicIO
|
|||
}
|
||||
readFully()
|
||||
}
|
||||
def standard: ProcessIO = new ProcessIO(ignoreOut, processFully(System.out.println), processFully(System.err.println))
|
||||
def connectToIn(o: OutputStream)
|
||||
{
|
||||
val printer = new PrintStream(o)
|
||||
try { processFully {x => printer.println(x); printer.flush() }(System.in) }
|
||||
catch { case _: InterruptedException => () }
|
||||
finally { printer.close() }
|
||||
}
|
||||
def input(connect: Boolean): OutputStream => Unit = if(connect) connectToIn else ignoreOut
|
||||
def standard(connectInput: Boolean): ProcessIO = standard(input(connectInput))
|
||||
def standard(in: OutputStream => Unit): ProcessIO = new ProcessIO(in, processFully(System.out.println), processFully(System.err.println))
|
||||
|
||||
def transferFully(in: InputStream, out: OutputStream)
|
||||
{
|
||||
|
|
@ -81,15 +94,20 @@ private abstract class AbstractProcessBuilder extends ProcessBuilder
|
|||
def #> (f: File): ProcessBuilder = new PipedProcessBuilder(this, new FileOutput(f, false), false)
|
||||
def #>> (f: File): ProcessBuilder = new PipedProcessBuilder(this, new FileOutput(f, true), true)
|
||||
|
||||
def run(): Process = run(BasicIO.standard)
|
||||
def run(log: Logger): Process = run(BasicIO(log))
|
||||
def run(): Process = run(false)
|
||||
def run(connectInput: Boolean): Process = run(BasicIO.standard(connectInput))
|
||||
def run(log: Logger): Process = run(log, false)
|
||||
def run(log: Logger, connectInput: Boolean): Process = run(BasicIO(log, connectInput))
|
||||
|
||||
def ! = run().exitValue()
|
||||
def !(log: Logger) =
|
||||
def ! = run(false).exitValue()
|
||||
def !< = run(true).exitValue()
|
||||
def !(log: Logger) = runBuffered(log, false)
|
||||
def !<(log: Logger) = runBuffered(log, true)
|
||||
private[this] def runBuffered(log: Logger, connectInput: Boolean) =
|
||||
{
|
||||
val log2 = new BufferedLogger(log)
|
||||
log2.startRecording()
|
||||
try { run(log2).exitValue() }
|
||||
try { run(log2, connectInput).exitValue() }
|
||||
finally { log2.playAll(); log2.clearAll() }
|
||||
}
|
||||
def !(io: ProcessIO) = run(io).exitValue()
|
||||
|
|
@ -320,19 +338,23 @@ private[sbt] class SimpleProcessBuilder(p: JProcessBuilder) extends AbstractProc
|
|||
Spawn(processError(process.getErrorStream)) :: Nil
|
||||
else
|
||||
Nil
|
||||
new SimpleProcess(process, inThread :: outThread :: errorThread)
|
||||
new SimpleProcess(process, inThread, outThread :: errorThread)
|
||||
}
|
||||
override def toString = p.command.toString
|
||||
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 SimpleProcess(p: JProcess, ioThreads: Iterable[Thread]) extends Process
|
||||
/** A thin wrapper around a java.lang.Process. `outputThreads` are the Threads created to read from the
|
||||
* output and error streams of the process. `inputThread` is the Thread created to write to the input stream of
|
||||
* the process.
|
||||
* The implementation of `exitValue` interrupts `inputThread` and then waits until all I/O threads die before
|
||||
* returning. */
|
||||
private class SimpleProcess(p: JProcess, inputThread: Thread, outputThreads: List[Thread]) extends Process
|
||||
{
|
||||
override def exitValue() =
|
||||
{
|
||||
p.waitFor() // wait for the process to terminate
|
||||
ioThreads.foreach(_.join()) // this ensures that all output is complete before returning (waitFor does not ensure this)
|
||||
inputThread.interrupt() // we interrupt the input thread to notify it that it can terminate
|
||||
(inputThread :: outputThreads).foreach(_.join()) // this ensures that all output is complete before returning (waitFor does not ensure this)
|
||||
p.exitValue()
|
||||
}
|
||||
override def destroy() = p.destroy()
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import scala.tools.nsc.{Interpreter, Settings}
|
|||
|
||||
class Foo {
|
||||
val settings = new Settings()
|
||||
settings.classpath.value = new java.io.File(classOf[Holder].getProtectionDomain.getCodeSource.getLocation.toURI).getAbsolutePath
|
||||
settings.classpath.value = FileUtilities.toFile(classOf[Holder].getProtectionDomain.getCodeSource.getLocation).getAbsolutePath
|
||||
val inter = new Interpreter(settings)
|
||||
|
||||
def eval(code: String): Any = {
|
||||
|
|
|
|||
|
|
@ -86,6 +86,6 @@ object ProcessSpecification extends Properties("Process I/O")
|
|||
"java -cp " + thisClasspath + " " + command
|
||||
}
|
||||
private def getSource[T](implicit mf: scala.reflect.Manifest[T]): String =
|
||||
(new File(mf.erasure.getProtectionDomain.getCodeSource.getLocation.toURI)).getAbsolutePath
|
||||
(FileUtilities.toFile(mf.erasure.getProtectionDomain.getCodeSource.getLocation)).getAbsolutePath
|
||||
}
|
||||
private trait SourceTag
|
||||
Loading…
Reference in New Issue