From 410a8dd4b1263db257b2b11e0145dd79f9c34b26 Mon Sep 17 00:00:00 2001 From: Ethan Atkins Date: Sun, 20 Sep 2020 14:56:49 -0700 Subject: [PATCH] Use jline-terminal-jna for sbt server The old sbt launcher uses jansi 1.11, which is incompatible with jline3. To work around this, we can use the jna terminal implementation for the jline system terminal. This commit also switches to using the jline TerminalBuilder for all system terminals except for the windows system terminal with the thin client. The jline terminal builder uses reflection that is difficult to make work with the thin client and it is much easier to just manually construct the thin client. This is only necessary for windows because on posix the thin client will fall back to an implementation that shells out for stty commands. --- build.sbt | 1 + .../main/scala/sbt/internal/util/JLine3.scala | 66 ++++++++----------- .../java/sbt/internal/MetaBuildLoader.java | 9 +-- 3 files changed, 34 insertions(+), 42 deletions(-) diff --git a/build.sbt b/build.sbt index ccb8e900d..40902658f 100644 --- a/build.sbt +++ b/build.sbt @@ -368,6 +368,7 @@ lazy val utilLogging = (project in file("internal") / "util-logging") Seq( jline, jline3Terminal, + jline3JNA, jline3Jansi, log4jApi, log4jCore, 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 80d93f303..75e189896 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 @@ -12,12 +12,13 @@ import java.nio.charset.Charset import java.util.{ Arrays, EnumSet } import java.util.concurrent.atomic.{ AtomicBoolean, AtomicReference } import org.jline.utils.InfoCmp.Capability -import org.jline.utils.{ ClosedException, NonBlockingReader, OSUtils } +import org.jline.utils.{ ClosedException, NonBlockingReader } import org.jline.terminal.{ Attributes, Size, Terminal => JTerminal } import org.jline.terminal.Terminal.SignalHandler import org.jline.terminal.impl.{ AbstractTerminal, DumbTerminal } import org.jline.terminal.impl.jansi.JansiSupportImpl import org.jline.terminal.impl.jansi.win.JansiWinSysTerminal +import org.jline.utils.OSUtils import scala.collection.JavaConverters._ import scala.util.Try import java.util.concurrent.LinkedBlockingQueue @@ -30,47 +31,36 @@ private[sbt] object JLine3 { } .toMap - private[util] def system = { - /* - * For reasons that are unclear to me, TerminalBuilder fails to build - * windows terminals. The instructions about the classpath did not work: - * https://stackoverflow.com/questions/52851232/jline3-issues-with-windows-terminal - * We can deconstruct what TerminalBuilder does and inline it for now. - * It is possible that this workaround will break WSL but I haven't checked that. - */ - if (Util.isNonCygwinWindows) { - val support = new JansiSupportImpl - val winConsole = support.isWindowsConsole(); - try { - val term = JansiWinSysTerminal.createTerminal( - "console", - "ansi", - OSUtils.IS_CONEMU, - Charset.forName("UTF-8"), - -1, - false, - SignalHandler.SIG_DFL, - true - ) - term.disableScrolling() - term - } catch { - case _: Exception => - org.jline.terminal.TerminalBuilder - .builder() - .system(false) - .paused(true) - .jansi(true) - .streams(Terminal.console.inputStream, Terminal.console.outputStream) - .build() - } - } else { + private[this] val forceWindowsJansiHolder = new AtomicBoolean(false) + private[sbt] def forceWindowsJansi(): Unit = forceWindowsJansiHolder.set(true) + private[this] def windowsJansi(): org.jline.terminal.Terminal = { + val support = new JansiSupportImpl + val winConsole = support.isWindowsConsole(); + val termType = sys.props.get("org.jline.terminal.type").orElse(sys.env.get("TERM")).orNull + val term = JansiWinSysTerminal.createTerminal( + "console", + termType, + OSUtils.IS_CONEMU, + Charset.forName("UTF-8"), + -1, + false, + SignalHandler.SIG_DFL, + true + ) + term.disableScrolling() + term + } + private[util] def system: org.jline.terminal.Terminal = { + if (forceWindowsJansiHolder.get) windowsJansi() + else { + // Only use jna on windows. Both jna and jansi use illegal reflective + // accesses on posix system. org.jline.terminal.TerminalBuilder .builder() .system(System.console != null) + .jna(Util.isNonCygwinWindows) + .jansi(false) .paused(true) - .jna(false) - .jansi(true) .build() } } diff --git a/main/src/main/java/sbt/internal/MetaBuildLoader.java b/main/src/main/java/sbt/internal/MetaBuildLoader.java index 2ca2a5088..08a80aa24 100644 --- a/main/src/main/java/sbt/internal/MetaBuildLoader.java +++ b/main/src/main/java/sbt/internal/MetaBuildLoader.java @@ -64,12 +64,13 @@ public final class MetaBuildLoader extends URLClassLoader { * library. */ public static MetaBuildLoader makeLoader(final AppProvider appProvider) throws IOException { - final Pattern pattern = - Pattern.compile( - "^(test-interface-[0-9.]+|jline-(terminal-)?[0-9.]+-sbt-.*|jansi-[0-9.]+)\\.jar"); + final String jlineJars = "jline-(terminal-)?[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); final File[] cp = appProvider.mainClasspath(); final URL[] interfaceURLs = new URL[1]; - final URL[] jlineURLs = new URL[3]; + final URL[] jlineURLs = new URL[7]; final File[] extra = appProvider.id().classpathExtra() == null ? new File[0] : appProvider.id().classpathExtra(); final Set bottomClasspath = new LinkedHashSet<>();