diff --git a/launch/src/main/scala/xsbt/boot/Boot.scala b/launch/src/main/scala/xsbt/boot/Boot.scala index ed60e0027..afc70c2d7 100644 --- a/launch/src/main/scala/xsbt/boot/Boot.scala +++ b/launch/src/main/scala/xsbt/boot/Boot.scala @@ -17,7 +17,6 @@ object Boot System.clearProperty("scala.home") // avoid errors from mixing Scala versions in the same JVM System.setProperty("jline.shutdownhook", "false") CheckProxy() - initJansi() run(args) } } @@ -47,19 +46,4 @@ object Boot } private def exit(code: Int): Nothing = System.exit(code).asInstanceOf[Nothing] - - private def initJansi() { - try { - val c = Class.forName("org.fusesource.jansi.AnsiConsole") - c.getMethod("systemInstall").invoke(null) - } catch { - case ignore: ClassNotFoundException => - /* The below code intentionally traps everything. It technically shouldn't trap the - * non-StackOverflowError VirtualMachineErrors and AWTError would be weird, but this is PermGen - * mitigation code that should not render sbt completely unusable if jansi initialization fails. - * [From Mark Harrah, https://github.com/sbt/sbt/pull/633#issuecomment-11957578]. - */ - case ex: Throwable => println("Jansi found on class path but initialization failed: " + ex) - } - } } diff --git a/launch/src/main/scala/xsbt/boot/BootConfiguration.scala b/launch/src/main/scala/xsbt/boot/BootConfiguration.scala index e64af227c..5170b61fa 100644 --- a/launch/src/main/scala/xsbt/boot/BootConfiguration.scala +++ b/launch/src/main/scala/xsbt/boot/BootConfiguration.scala @@ -20,6 +20,7 @@ private object BootConfiguration val LibraryModuleName = "scala-library" val JUnitName = "junit" + val JAnsiVersion = "1.11" val SbtOrg = "org.scala-sbt" diff --git a/launch/src/main/scala/xsbt/boot/JAnsi.scala b/launch/src/main/scala/xsbt/boot/JAnsi.scala new file mode 100644 index 000000000..e9c6a5ff0 --- /dev/null +++ b/launch/src/main/scala/xsbt/boot/JAnsi.scala @@ -0,0 +1,24 @@ +package xsbt.boot + + import Pre._ + +object JAnsi +{ + def uninstall(loader: ClassLoader): Unit = callJAnsi("systemUninstall", loader) + def install(loader: ClassLoader): Unit = callJAnsi("systemInstall", loader) + + private[this] def callJAnsi(methodName: String, loader: ClassLoader): Unit = if(isWindows && !isCygwin) callJAnsiMethod(methodName, loader) + private[this] def callJAnsiMethod(methodName: String, loader: ClassLoader): Unit = + try { + val c = Class.forName("org.fusesource.jansi.AnsiConsole", true, loader) + c.getMethod(methodName).invoke(null) + } catch { + case ignore: ClassNotFoundException => + /* The below code intentionally traps everything. It technically shouldn't trap the + * non-StackOverflowError VirtualMachineErrors and AWTError would be weird, but this is PermGen + * mitigation code that should not render sbt completely unusable if jansi initialization fails. + * [From Mark Harrah, https://github.com/sbt/sbt/pull/633#issuecomment-11957578]. + */ + case ex: Throwable => println("Jansi found on class path but initialization failed: " + ex) + } +} \ No newline at end of file diff --git a/launch/src/main/scala/xsbt/boot/Launch.scala b/launch/src/main/scala/xsbt/boot/Launch.scala index 6144646d1..0359960e0 100644 --- a/launch/src/main/scala/xsbt/boot/Launch.scala +++ b/launch/src/main/scala/xsbt/boot/Launch.scala @@ -4,7 +4,7 @@ package xsbt.boot import Pre._ -import BootConfiguration.{CompilerModuleName, LibraryModuleName} +import BootConfiguration.{CompilerModuleName, JAnsiVersion, LibraryModuleName} import java.io.File import java.net.{URL, URLClassLoader} import java.util.concurrent.Callable @@ -51,9 +51,14 @@ object Launch val appProvider: xsbti.AppProvider = launcher.app(app, orNull(scalaVersion)) // takes ~40 ms when no update is required val appConfig: xsbti.AppConfiguration = new AppConfiguration(toArray(arguments), workingDirectory, appProvider) - val main = appProvider.newMain() - try { withContextLoader(appProvider.loader)(main.run(appConfig)) } - catch { case e: xsbti.FullReload => if(e.clean) delete(launcher.bootDirectory); throw e } + JAnsi.install(launcher.topLoader) + try { + val main = appProvider.newMain() + try { withContextLoader(appProvider.loader)(main.run(appConfig)) } + catch { case e: xsbti.FullReload => if(e.clean) delete(launcher.bootDirectory); throw e } + } finally { + JAnsi.uninstall(launcher.topLoader) + } } final def launch(run: RunConfiguration => xsbti.MainResult)(config: RunConfiguration): Option[Int] = { @@ -88,7 +93,7 @@ class Launch private[xsbt](val bootDirectory: File, val lockBoot: Boolean, val i getAppProvider(id, scalaVersion, false) val bootLoader = new BootFilteredLoader(getClass.getClassLoader) - val topLoader = jnaLoader(bootLoader) + val topLoader = if(isWindows && !isCygwin) jansiLoader(bootLoader) else bootLoader val updateLockFile = if(lockBoot) Some(new File(bootDirectory, "sbt.boot.lock")) else None @@ -99,19 +104,20 @@ class Launch private[xsbt](val bootDirectory: File, val lockBoot: Boolean, val i def isOverrideRepositories: Boolean = ivyOptions.isOverrideRepositories def checksums = checksumsList.toArray[String] - def jnaLoader(parent: ClassLoader): ClassLoader = + // JAnsi needs to be shared between Scala and the application so there aren't two competing versions + def jansiLoader(parent: ClassLoader): ClassLoader = { - val id = AppID("net.java.dev.jna", "jna", "3.2.3", "", toArray(Nil), xsbti.CrossValue.Disabled, array()) + val id = AppID("org.fusesource.jansi", "jansi", JAnsiVersion, "", toArray(Nil), xsbti.CrossValue.Disabled, array()) val configuration = makeConfiguration(ScalaOrg, None) - val jnaHome = appDirectory(new File(bootDirectory, baseDirectoryName(ScalaOrg, None)), id) - val module = appModule(id, None, false, "jna") + val jansiHome = appDirectory(new File(bootDirectory, baseDirectoryName(ScalaOrg, None)), id) + val module = appModule(id, None, false, "jansi") def makeLoader(): ClassLoader = { - val urls = toURLs(wrapNull(jnaHome.listFiles(JarFilter))) + val urls = toURLs(wrapNull(jansiHome.listFiles(JarFilter))) val loader = new URLClassLoader(urls, bootLoader) - checkLoader(loader, module, "com.sun.jna.Function" :: Nil, loader) + checkLoader(loader, module, "org.fusesource.jansi.internal.WindowsSupport" :: Nil, loader) } val existingLoader = - if(jnaHome.exists) + if(jansiHome.exists) try Some(makeLoader()) catch { case e: Exception => None } else None diff --git a/launch/src/main/scala/xsbt/boot/Pre.scala b/launch/src/main/scala/xsbt/boot/Pre.scala index 011f6d2f6..73440d745 100644 --- a/launch/src/main/scala/xsbt/boot/Pre.scala +++ b/launch/src/main/scala/xsbt/boot/Pre.scala @@ -79,4 +79,6 @@ object Pre } if(f.exists) f.delete() } + final val isWindows: Boolean = System.getProperty("os.name").toLowerCase.contains("windows") + final val isCygwin: Boolean = isWindows && java.lang.Boolean.getBoolean("sbt.cygwin") } diff --git a/project/Sbt.scala b/project/Sbt.scala index bc0d96948..0e359c164 100644 --- a/project/Sbt.scala +++ b/project/Sbt.scala @@ -62,9 +62,9 @@ object Sbt extends Build // Utilities related to reflection, managing Scala versions, and custom class loaders lazy val classpathSub = testedBaseProject(utilPath / "classpath", "Classpath") dependsOn(launchInterfaceSub, interfaceSub, ioSub) settings(scalaCompiler) // Command line-related utilities. - lazy val completeSub = testedBaseProject(utilPath / "complete", "Completion") dependsOn(collectionSub, controlSub, ioSub) settings(jline : _*) + lazy val completeSub = testedBaseProject(utilPath / "complete", "Completion") dependsOn(collectionSub, controlSub, ioSub) settings(jline) // logging - lazy val logSub = testedBaseProject(utilPath / "log", "Logging") dependsOn(interfaceSub, processSub) settings(jline : _*) + lazy val logSub = testedBaseProject(utilPath / "log", "Logging") dependsOn(interfaceSub, processSub) settings(jline) // Relation lazy val relationSub = testedBaseProject(utilPath / "relation", "Relation") dependsOn(interfaceSub, processSub) // class file reader and analyzer diff --git a/project/Util.scala b/project/Util.scala index 230deff20..71c85f385 100644 --- a/project/Util.scala +++ b/project/Util.scala @@ -156,8 +156,7 @@ object Common { def lib(m: ModuleID) = libraryDependencies += m lazy val jlineDep = "jline" % "jline" % "2.11" - lazy val jansiDep = "org.fusesource.jansi" % "jansi" % "1.9" // jline pom doesn't explicitly declare it? - lazy val jline = Seq(lib(jlineDep), lib(jansiDep)) + lazy val jline = lib(jlineDep) lazy val ivy = lib("org.apache.ivy" % "ivy" % "2.3.0-rc1") lazy val httpclient = lib("commons-httpclient" % "commons-httpclient" % "3.1") lazy val jsch = lib("com.jcraft" % "jsch" % "0.1.46" intransitive() ) diff --git a/util/complete/src/main/scala/sbt/LineReader.scala b/util/complete/src/main/scala/sbt/LineReader.scala index 6d81a2391..e3d3df0f0 100644 --- a/util/complete/src/main/scala/sbt/LineReader.scala +++ b/util/complete/src/main/scala/sbt/LineReader.scala @@ -64,6 +64,24 @@ abstract class JLine extends LineReader } private object JLine { + private[this] val TerminalProperty = "jline.terminal" + + fixTerminalProperty() + + // translate explicit class names to type in order to support + // older Scala, since it shaded classes but not the system property + private[sbt] def fixTerminalProperty() { + val newValue = System.getProperty(TerminalProperty) match { + case "jline.UnixTerminal" => "unix" + case null if System.getProperty("sbt.cygwin") != null => "unix" + case "jline.WindowsTerminal" => "windows" + case "jline.AnsiWindowsTerminal" => "windows" + case "jline.UnsupportedTerminal" => "none" + case x => x + } + if(newValue != null) System.setProperty(TerminalProperty, newValue) + } + // When calling this, ensure that enableEcho has been or will be called. // TerminalFactory.get will initialize the terminal to disable echo. private def terminal = jline.TerminalFactory.get