From 7b2b7d696ba1f1046f950195f100b4ab4b9c088a Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Sat, 22 Jul 2017 03:34:10 -0400 Subject: [PATCH 1/2] Don't wrap InputStream for Windows Ref #3282 We used to wrap InputStream so it will inject Thread.sleep, which then allows the thread to be cancelled, emulating a non-blocking readLine. This trick doesn't seem to work for Windows. For non-Cygwin, actually just removing the wrapping does the job, but I couldn't get it to work for Cygwin. To test, run some command via network, and then type `show name` into the terminal. On Cygwin, it will not respond. --- build.sbt | 3 +- .../main/scala/sbt/internal/util/Util.scala | 14 ++++++++++ .../scala/sbt/internal/util/LineReader.scala | 28 +++++++++++-------- main/src/main/scala/sbt/Resolvers.scala | 11 ++------ run/src/main/scala/sbt/Fork.scala | 6 ++-- server.md | 3 ++ 6 files changed, 40 insertions(+), 25 deletions(-) diff --git a/build.sbt b/build.sbt index f8058799f..6f44b70a0 100644 --- a/build.sbt +++ b/build.sbt @@ -203,6 +203,7 @@ lazy val stdTaskProj = (project in file("tasks-standard")) // Embedded Scala code runner lazy val runProj = (project in file("run")) .enablePlugins(ContrabandPlugin) + .dependsOn(collectionProj) .settings( testedBaseSettings, name := "Run", @@ -335,7 +336,7 @@ lazy val mainSettingsProj = (project in file("main-settings")) // The main integration project for sbt. It brings all of the projects together, configures them, and provides for overriding conventions. lazy val mainProj = (project in file("main")) .enablePlugins(ContrabandPlugin) - .dependsOn(logicProj, actionsProj, mainSettingsProj, runProj, commandProj) + .dependsOn(logicProj, actionsProj, mainSettingsProj, runProj, commandProj, collectionProj) .settings( testedBaseSettings, name := "Main", diff --git a/internal/util-collection/src/main/scala/sbt/internal/util/Util.scala b/internal/util-collection/src/main/scala/sbt/internal/util/Util.scala index 802457d11..5736f57c2 100644 --- a/internal/util-collection/src/main/scala/sbt/internal/util/Util.scala +++ b/internal/util-collection/src/main/scala/sbt/internal/util/Util.scala @@ -37,4 +37,18 @@ object Util { Camel.replaceAllIn(s, m => m.group(1) + "-" + m.group(2).toLowerCase(Locale.ENGLISH)) def quoteIfKeyword(s: String): String = if (ScalaKeywords.values(s)) '`' + s + '`' else s + + lazy val isWindows: Boolean = + System.getProperty("os.name").toLowerCase(Locale.ENGLISH).contains("windows") + + lazy val isCygwin: Boolean = { + val os = Option(System.getenv("OSTYPE")) + os match { + case Some(x) => x.toLowerCase(Locale.ENGLISH).contains("cygwin") + case _ => false + } + } + + lazy val isNonCygwinWindows: Boolean = isWindows && !isCygwin + lazy val isCygwinWindows: Boolean = isWindows && isCygwin } diff --git a/internal/util-complete/src/main/scala/sbt/internal/util/LineReader.scala b/internal/util-complete/src/main/scala/sbt/internal/util/LineReader.scala index d9ad18859..6dc0936d4 100644 --- a/internal/util-complete/src/main/scala/sbt/internal/util/LineReader.scala +++ b/internal/util-complete/src/main/scala/sbt/internal/util/LineReader.scala @@ -14,12 +14,22 @@ abstract class JLine extends LineReader { protected[this] def handleCONT: Boolean protected[this] def reader: ConsoleReader protected[this] def injectThreadSleep: Boolean - protected[this] val in: InputStream = JLine.makeInputStream(injectThreadSleep) - - def readLine(prompt: String, mask: Option[Char] = None) = JLine.withJLine { - unsynchronizedReadLine(prompt, mask) + protected[this] lazy val in: InputStream = { + // On Windows InputStream#available doesn't seem to return positive number. + JLine.makeInputStream(injectThreadSleep && !Util.isNonCygwinWindows) } + def readLine(prompt: String, mask: Option[Char] = None) = + try { + JLine.withJLine { + unsynchronizedReadLine(prompt, mask) + } + } catch { + case _: InterruptedException => + // println("readLine: InterruptedException") + Option("") + } + private[this] def unsynchronizedReadLine(prompt: String, mask: Option[Char]): Option[String] = readLineWithHistory(prompt, mask) map { x => x.trim @@ -42,13 +52,9 @@ abstract class JLine extends LineReader { private[this] def readLineDirectRaw(prompt: String, mask: Option[Char]): Option[String] = { val newprompt = handleMultilinePrompt(prompt) - try { - mask match { - case Some(m) => Option(reader.readLine(newprompt, m)) - case None => Option(reader.readLine(newprompt)) - } - } catch { - case e: InterruptedException => Option("") + mask match { + case Some(m) => Option(reader.readLine(newprompt, m)) + case None => Option(reader.readLine(newprompt)) } } diff --git a/main/src/main/scala/sbt/Resolvers.scala b/main/src/main/scala/sbt/Resolvers.scala index d2935a98c..6f45f935c 100644 --- a/main/src/main/scala/sbt/Resolvers.scala +++ b/main/src/main/scala/sbt/Resolvers.scala @@ -17,6 +17,7 @@ import java.util.Locale import scala.sys.process.Process import scala.util.control.NonFatal +import sbt.internal.util.Util object Resolvers { type Resolver = BuildLoader.Resolver @@ -125,20 +126,12 @@ object Resolvers { private def normalized(uri: URI) = uri.copy(scheme = scheme) } - private lazy val onWindows = { - val os = System.getenv("OSTYPE") - val isCygwin = (os != null) && os.toLowerCase(Locale.ENGLISH).contains("cygwin") - val isWindows = - System.getProperty("os.name", "").toLowerCase(Locale.ENGLISH).contains("windows") - isWindows && !isCygwin - } - def run(command: String*): Unit = run(None, command: _*) def run(cwd: Option[File], command: String*): Unit = { val result = Process( - if (onWindows) "cmd" +: "/c" +: command + if (Util.isNonCygwinWindows) "cmd" +: "/c" +: command else command, cwd ) !; diff --git a/run/src/main/scala/sbt/Fork.scala b/run/src/main/scala/sbt/Fork.scala index 4ce6cfffc..80839181e 100644 --- a/run/src/main/scala/sbt/Fork.scala +++ b/run/src/main/scala/sbt/Fork.scala @@ -4,9 +4,9 @@ package sbt import java.io.File -import java.util.Locale import scala.sys.process.Process import OutputStrategy._ +import sbt.internal.util.Util /** * Represents a command that can be forked. @@ -80,15 +80,13 @@ object Fork { private[this] val MaxConcatenatedOptionLength = 5000 private def fitClasspath(options: Seq[String]): (Option[String], Seq[String]) = - if (isWindows && optionsTooLong(options)) + if (Util.isWindows && optionsTooLong(options)) convertClasspathToEnv(options) else (None, options) private[this] def optionsTooLong(options: Seq[String]): Boolean = options.mkString(" ").length > MaxConcatenatedOptionLength - private[this] val isWindows: Boolean = - System.getProperty("os.name").toLowerCase(Locale.ENGLISH).contains("windows") private[this] def convertClasspathToEnv(options: Seq[String]): (Option[String], Seq[String]) = { val (preCP, cpAndPost) = options.span(opt => !isClasspathOption(opt)) val postCP = cpAndPost.drop(2) diff --git a/server.md b/server.md index ddaa9b034..20c55f508 100644 --- a/server.md +++ b/server.md @@ -6,3 +6,6 @@ { "type": "ExecCommand", "commandLine": "compile" } ``` +```json +{ "type": "ExecCommand", "commandLine": "eval Thread.sleep(10000)" } +``` From a58b1ebce0ffdbff7378e0a240092bb7d00b43c0 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Sat, 22 Jul 2017 03:35:42 -0400 Subject: [PATCH 2/2] Keep ConsoleChannel open Fixes #3282 For now, don't try to shutdown ConsoleChannel while the network is processing the command in the background. --- main-command/src/main/scala/sbt/internal/ConsoleChannel.scala | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/main-command/src/main/scala/sbt/internal/ConsoleChannel.scala b/main-command/src/main/scala/sbt/internal/ConsoleChannel.scala index b4a32270c..6b6078ff9 100644 --- a/main-command/src/main/scala/sbt/internal/ConsoleChannel.scala +++ b/main-command/src/main/scala/sbt/internal/ConsoleChannel.scala @@ -48,7 +48,9 @@ private[sbt] final class ConsoleChannel(val name: String) extends CommandChannel case Some(src) if src.channelName != name => askUserThread match { case Some(x) => - shutdown() + // keep listening while network-origin command is running + // make sure to test Windows and Cygwin, if you uncomment + // shutdown() case _ => } case _ =>