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.
This commit is contained in:
Ethan Atkins 2020-09-20 14:56:49 -07:00
parent 09599e4863
commit 410a8dd4b1
3 changed files with 34 additions and 42 deletions

View File

@ -368,6 +368,7 @@ lazy val utilLogging = (project in file("internal") / "util-logging")
Seq( Seq(
jline, jline,
jline3Terminal, jline3Terminal,
jline3JNA,
jline3Jansi, jline3Jansi,
log4jApi, log4jApi,
log4jCore, log4jCore,

View File

@ -12,12 +12,13 @@ import java.nio.charset.Charset
import java.util.{ Arrays, EnumSet } import java.util.{ Arrays, EnumSet }
import java.util.concurrent.atomic.{ AtomicBoolean, AtomicReference } import java.util.concurrent.atomic.{ AtomicBoolean, AtomicReference }
import org.jline.utils.InfoCmp.Capability 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.{ Attributes, Size, Terminal => JTerminal }
import org.jline.terminal.Terminal.SignalHandler import org.jline.terminal.Terminal.SignalHandler
import org.jline.terminal.impl.{ AbstractTerminal, DumbTerminal } import org.jline.terminal.impl.{ AbstractTerminal, DumbTerminal }
import org.jline.terminal.impl.jansi.JansiSupportImpl import org.jline.terminal.impl.jansi.JansiSupportImpl
import org.jline.terminal.impl.jansi.win.JansiWinSysTerminal import org.jline.terminal.impl.jansi.win.JansiWinSysTerminal
import org.jline.utils.OSUtils
import scala.collection.JavaConverters._ import scala.collection.JavaConverters._
import scala.util.Try import scala.util.Try
import java.util.concurrent.LinkedBlockingQueue import java.util.concurrent.LinkedBlockingQueue
@ -30,47 +31,36 @@ private[sbt] object JLine3 {
} }
.toMap .toMap
private[util] def system = { private[this] val forceWindowsJansiHolder = new AtomicBoolean(false)
/* private[sbt] def forceWindowsJansi(): Unit = forceWindowsJansiHolder.set(true)
* For reasons that are unclear to me, TerminalBuilder fails to build private[this] def windowsJansi(): org.jline.terminal.Terminal = {
* windows terminals. The instructions about the classpath did not work: val support = new JansiSupportImpl
* https://stackoverflow.com/questions/52851232/jline3-issues-with-windows-terminal val winConsole = support.isWindowsConsole();
* We can deconstruct what TerminalBuilder does and inline it for now. val termType = sys.props.get("org.jline.terminal.type").orElse(sys.env.get("TERM")).orNull
* It is possible that this workaround will break WSL but I haven't checked that. val term = JansiWinSysTerminal.createTerminal(
*/ "console",
if (Util.isNonCygwinWindows) { termType,
val support = new JansiSupportImpl OSUtils.IS_CONEMU,
val winConsole = support.isWindowsConsole(); Charset.forName("UTF-8"),
try { -1,
val term = JansiWinSysTerminal.createTerminal( false,
"console", SignalHandler.SIG_DFL,
"ansi", true
OSUtils.IS_CONEMU, )
Charset.forName("UTF-8"), term.disableScrolling()
-1, term
false, }
SignalHandler.SIG_DFL, private[util] def system: org.jline.terminal.Terminal = {
true if (forceWindowsJansiHolder.get) windowsJansi()
) else {
term.disableScrolling() // Only use jna on windows. Both jna and jansi use illegal reflective
term // accesses on posix system.
} catch {
case _: Exception =>
org.jline.terminal.TerminalBuilder
.builder()
.system(false)
.paused(true)
.jansi(true)
.streams(Terminal.console.inputStream, Terminal.console.outputStream)
.build()
}
} else {
org.jline.terminal.TerminalBuilder org.jline.terminal.TerminalBuilder
.builder() .builder()
.system(System.console != null) .system(System.console != null)
.jna(Util.isNonCygwinWindows)
.jansi(false)
.paused(true) .paused(true)
.jna(false)
.jansi(true)
.build() .build()
} }
} }

View File

@ -64,12 +64,13 @@ public final class MetaBuildLoader extends URLClassLoader {
* library. * library.
*/ */
public static MetaBuildLoader makeLoader(final AppProvider appProvider) throws IOException { public static MetaBuildLoader makeLoader(final AppProvider appProvider) throws IOException {
final Pattern pattern = final String jlineJars = "jline-(terminal-)?[0-9.]+-sbt-.*|jline-terminal-(jna|jansi)-[0-9.]+";
Pattern.compile( final String fullPattern =
"^(test-interface-[0-9.]+|jline-(terminal-)?[0-9.]+-sbt-.*|jansi-[0-9.]+)\\.jar"); "^(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 File[] cp = appProvider.mainClasspath();
final URL[] interfaceURLs = new URL[1]; final URL[] interfaceURLs = new URL[1];
final URL[] jlineURLs = new URL[3]; final URL[] jlineURLs = new URL[7];
final File[] extra = final File[] extra =
appProvider.id().classpathExtra() == null ? new File[0] : appProvider.id().classpathExtra(); appProvider.id().classpathExtra() == null ? new File[0] : appProvider.id().classpathExtra();
final Set<File> bottomClasspath = new LinkedHashSet<>(); final Set<File> bottomClasspath = new LinkedHashSet<>();