From 927151485df60dbf15c506ae4917a85af62afb94 Mon Sep 17 00:00:00 2001 From: Ethan Atkins Date: Wed, 18 Nov 2020 08:12:18 -0800 Subject: [PATCH 1/2] Exclude jansi classes from metabuild top loader The launcher embeds a fixed version of jansi above the rest of the classpath on windows. This causes problems for the scala 2.12 console because it tries to load methods that don't exist from the old jansi jar. This can be fixed by excluding all jansi classes from the top loader. We also need to exclude jansi classes in the scala instance top class loader to make the 2.10 console work because scala 2.10 uses a shaded jline that requires a very old jansi version. Due to the shading, the thin client doesn't work with the 2.10 console. --- .../main/java/sbt/internal/MetaBuildLoader.java | 15 +++++++++++++++ main/src/main/scala/sbt/Defaults.scala | 11 ++++++++++- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/main/src/main/java/sbt/internal/MetaBuildLoader.java b/main/src/main/java/sbt/internal/MetaBuildLoader.java index 08a80aa24..5c83ceed5 100644 --- a/main/src/main/java/sbt/internal/MetaBuildLoader.java +++ b/main/src/main/java/sbt/internal/MetaBuildLoader.java @@ -117,6 +117,21 @@ public final class MetaBuildLoader extends URLClassLoader { if (!foundSBTLoader) topLoader = topLoader.getParent(); } if (topLoader == null) topLoader = scalaProvider.launcher().topLoader(); + // the bundled version of jansi with old versions of the launcher cause + // problems so we need to exclude it from classloading + topLoader = + new ClassLoader(topLoader) { + @Override + protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException { + if (name.startsWith("org.fusesource")) throw new ClassNotFoundException(name); + return super.loadClass(name, resolve); + } + + @Override + public String toString() { + return "JansiExclusionClassLoader"; + } + }; final TestInterfaceLoader interfaceLoader = new TestInterfaceLoader(interfaceURLs, topLoader); final JLineLoader jlineLoader = new JLineLoader(jlineURLs, interfaceLoader); diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index 8e5ab3f79..c6200785f 100644 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -1059,8 +1059,17 @@ object Defaults extends BuildCommon { classLoaderCache: ClassLoaderCache, topLoader: ClassLoader, ): ScalaInstance = { + // Scala 2.10 shades jline in the console so we need to make sure that it loads a compatible + // jansi version. Because of the shading, console does not work with the thin client for 2.10.x. + val jansiExclusionLoader = if (version.startsWith("2.10.")) new ClassLoader(topLoader) { + override protected def loadClass(name: String, resolve: Boolean): Class[_] = { + if (name.startsWith("org.fusesource")) throw new ClassNotFoundException(name) + super.loadClass(name, resolve) + } + } + else topLoader val allJarsDistinct = allJars.distinct - val libraryLoader = classLoaderCache(libraryJars.toList, topLoader) + val libraryLoader = classLoaderCache(libraryJars.toList, jansiExclusionLoader) val fullLoader = classLoaderCache(allJarsDistinct.toList, libraryLoader) new ScalaInstance( version, From c34678e9db60d23d3dcf41f6440c06c9ce6fa935 Mon Sep 17 00:00:00 2001 From: Ethan Atkins Date: Tue, 17 Nov 2020 13:19:01 -0800 Subject: [PATCH 2/2] Upgrade jline 3 We no longer need to use the forked version of jline because they have merged in our required changes. The latest version of jline does upgrade jansi, however, and some of the apis we were relying on for windows were removed so they had to be manually implemented. I verified that console input still worked on my windows vm after this change. --- .../main/scala/sbt/internal/util/JLine3.scala | 1 - .../sbt/internal/util/WindowsInputStream.scala | 18 +++++++++++++++++- .../java/sbt/internal/MetaBuildLoader.java | 2 +- project/Dependencies.scala | 8 ++++---- 4 files changed, 22 insertions(+), 7 deletions(-) diff --git a/internal/util-logging/src/main/scala/sbt/internal/util/JLine3.scala b/internal/util-logging/src/main/scala/sbt/internal/util/JLine3.scala index 3cb4342eb..64d84c1da 100644 --- a/internal/util-logging/src/main/scala/sbt/internal/util/JLine3.scala +++ b/internal/util-logging/src/main/scala/sbt/internal/util/JLine3.scala @@ -24,7 +24,6 @@ import org.jline.utils.OSUtils import scala.collection.JavaConverters._ import scala.util.Try import java.util.concurrent.LinkedBlockingQueue -import org.fusesource.jansi.internal.WindowsSupport private[sbt] object JLine3 { private[util] val initialAttributes = new AtomicReference[Attributes] diff --git a/internal/util-logging/src/main/scala/sbt/internal/util/WindowsInputStream.scala b/internal/util-logging/src/main/scala/sbt/internal/util/WindowsInputStream.scala index e3b6df0a1..486759cbb 100644 --- a/internal/util-logging/src/main/scala/sbt/internal/util/WindowsInputStream.scala +++ b/internal/util-logging/src/main/scala/sbt/internal/util/WindowsInputStream.scala @@ -10,11 +10,27 @@ package sbt.internal.util import java.io.InputStream import java.util.concurrent.LinkedBlockingQueue import java.util.concurrent.atomic.AtomicBoolean -import org.fusesource.jansi.internal.WindowsSupport +import org.fusesource.jansi.internal.Kernel32 import org.jline.utils.InfoCmp.Capability import scala.annotation.tailrec import Terminal.SimpleInputStream +private object WindowsSupport { + def getConsoleMode = { + val console = Kernel32.GetStdHandle(Kernel32.STD_INPUT_HANDLE); + val mode = new Array[Int](1); + if (Kernel32.GetConsoleMode(console, mode) == 0) -1 else mode.head + } + def setConsoleMode(mode: Int): Unit = { + val console = Kernel32.GetStdHandle(Kernel32.STD_INPUT_HANDLE); + Kernel32.SetConsoleMode(console, mode) + () + } + def readConsoleInput(count: Int) = { + val console = Kernel32.GetStdHandle(Kernel32.STD_INPUT_HANDLE); + Kernel32.readConsoleInputHelper(console, 1, false) + } +} /* * We need a special input stream for windows because special key events * like arrow keys are not reported by System.in. What makes this extra diff --git a/main/src/main/java/sbt/internal/MetaBuildLoader.java b/main/src/main/java/sbt/internal/MetaBuildLoader.java index 5c83ceed5..c91337a44 100644 --- a/main/src/main/java/sbt/internal/MetaBuildLoader.java +++ b/main/src/main/java/sbt/internal/MetaBuildLoader.java @@ -64,7 +64,7 @@ public final class MetaBuildLoader extends URLClassLoader { * library. */ public static MetaBuildLoader makeLoader(final AppProvider appProvider) throws IOException { - final String jlineJars = "jline-(terminal-)?[0-9.]+-sbt-.*|jline-terminal-(jna|jansi)-[0-9.]+"; + final String jlineJars = "jline-?[0-9.]+-sbt-.*|jline-terminal(-(jna|jansi))?-[0-9.]+"; final String fullPattern = "^(test-interface-[0-9.]+|" + jlineJars + "|jansi-[0-9.]+|jna-(platform-)?[0-9.]+)\\.jar"; final Pattern pattern = Pattern.compile(fullPattern); diff --git a/project/Dependencies.scala b/project/Dependencies.scala index f40163d8f..2dce163ae 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -84,14 +84,14 @@ object Dependencies { val sjsonNewScalaJson = sjsonNew("sjson-new-scalajson") val sjsonNewMurmurhash = sjsonNew("sjson-new-murmurhash") - val jline = "org.scala-sbt.jline" % "jline" % "2.14.7-sbt-5e51b9d4f9631ebfa29753ce4accc57808e7fd6b" - val jline3Version = "3.16.0" // Once the base jline version is upgraded, we can use the official jline-terminal - val jline3Terminal = "org.scala-sbt.jline3" % "jline-terminal" % s"$jline3Version-sbt-211a082ed6326908dc84ca017ce4430728f18a8a" + val jline = "org.scala-sbt.jline" % "jline" % "2.14.7-sbt-42b717d4418374417765c7651dca69b1b75d8b84" + val jline3Version = "3.17.1" + val jline3Terminal = "org.jline" % "jline-terminal" % jline3Version val jline3Jansi = "org.jline" % "jline-terminal-jansi" % jline3Version val jline3JNA = "org.jline" % "jline-terminal-jna" % jline3Version val jline3Reader = "org.jline" % "jline-reader" % jline3Version val jline3Builtins = "org.jline" % "jline-builtins" % jline3Version - val jansi = "org.fusesource.jansi" % "jansi" % "1.18" + val jansi = "org.fusesource.jansi" % "jansi" % "2.0.1" val scalatest = "org.scalatest" %% "scalatest" % "3.0.8" val scalacheck = "org.scalacheck" %% "scalacheck" % "1.14.0" val specs2 = "org.specs2" %% "specs2-junit" % "4.10.0"