* 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:
dmharrah 2009-07-07 23:54:12 +00:00
parent 8090c63519
commit c350ab6b3c
14 changed files with 114 additions and 51 deletions

View File

@ -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('!')

View File

@ -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)

View File

@ -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}
@ -787,11 +787,15 @@ object FileUtilities
writer.write(line)
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)

View File

@ -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)
}
}
private object JLine
{
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 =
{
val cr = new ConsoleReader
cr.setBellEnabled(false)
cr
}
protected[this] val reader = JLine.createReader()
}
class JLineReader(historyPath: Option[Path], completors: Completors, log: Logger) extends JLine
{

View File

@ -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.*/

View File

@ -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)
{

View File

@ -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

View File

@ -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 {

View File

@ -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

View File

@ -55,8 +55,10 @@ object Run extends ScalaRun
log.info("")
Control.trapUnit("Error during session: ", log)
{
val loop = new InterpreterLoop
executeTrapExit(loop.main(settings), log)
JLine.withJLine {
val loop = new InterpreterLoop
executeTrapExit(loop.main(settings), log)
}
}
}
}
@ -128,8 +130,10 @@ object Run extends ScalaRun
log.info("")
Control.trapUnit("Error during session: ", log)
{
val loop = new ProjectInterpreterLoop(compilerSettings, project)
executeTrapExit(loop.main(interpreterSettings), log)
JLine.withJLine {
val loop = new ProjectInterpreterLoop(compilerSettings, project)
executeTrapExit(loop.main(interpreterSettings), log)
}
}
}}
}

View File

@ -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

View File

@ -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()

View File

@ -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 = {
@ -24,4 +24,4 @@ object Test
val foo = new Foo
foo.eval("3")
}
}
}

View File

@ -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