diff options
author | Adriaan Moors <adriaan.moors@typesafe.com> | 2015-06-17 11:06:31 -0700 |
---|---|---|
committer | Adriaan Moors <adriaan.moors@typesafe.com> | 2015-06-18 10:33:33 -0700 |
commit | dfb70b632c1e8a2c6ce27eaacb74dbbb47ce9532 (patch) | |
tree | 277124732e329eb78b6168ad8ad3a6c908fb407f | |
parent | 43139faa4f4348b95907e06883f2fefb41ea3a3b (diff) | |
download | scala-dfb70b632c1e8a2c6ce27eaacb74dbbb47ce9532.tar.gz scala-dfb70b632c1e8a2c6ce27eaacb74dbbb47ce9532.tar.bz2 scala-dfb70b632c1e8a2c6ce27eaacb74dbbb47ce9532.zip |
SI-9339 Support classpaths with no single compatible jline
As usual, the repl will use whatever jline 2 jar on the classpath,
if there is one. Failing that, there's a fallback and an override.
If instantiating the standard `jline.InteractiveReader` fails,
we fall back to an embedded, shaded, version of jline,
provided by `jline_embedded.InteractiveReader`.
(Assume `import scala.tools.nsc.interpreter._` for this message.)
The instantiation of `InteractiveReader` eagerly exercises jline,
so that a linkage error will result if jline is missing or if the
provided one is not binary compatible.
The property `scala.repl.reader` overrides this behavior, if set to
the FQN of a class that looks like `YourInteractiveReader` below.
```
class YourInteractiveReader(completer: () => Completion) extends InteractiveReader
```
The repl logs which classes it tried to instantiate under `-Ydebug`.
# Changes to source & build
The core of the repl (`src/repl`) no longer depends on jline.
The jline interface is now in `src/repl-jline`.
The embedded jline + our interface to it are generated by the `quick.repl` target.
The build now also enforces that only `src/repl-jline` depends on jline.
The sources in `src/repl` are now sure to be independent of it,
though they do use reflection to instantiate a suitable subclass
of `InteractiveReader`, as explained above.
The `quick.repl` target builds the sources in `src/repl` and `src/repl-jline`,
producing a jar for the `repl-jline` classes, which is then transformed using
jarjar to obtain a shaded copy of the `scala.tools.nsc.interpreter.jline` package.
Jarjar is used to combine the `jline` jar and the `repl-jline` into a new jar,
rewriting package names as follows:
- `org.fusesource` -> `scala.tools.fusesource_embedded`
- `jline` -> `scala.tools.jline_embedded`
- `scala.tools.nsc.interpreter.jline` -> `scala.tools.nsc.interpreter.jline_embedded`
Classes not reachable from `scala.tools.**` are pruned, as well as empty dirs.
The classes in the `repl-jline` jar as well as those in the rewritten one
are copied to the repl's output directory.
PS: The sbt build is not updated, sorry.
PPS: A more recent fork of jarjar: https://github.com/shevek/jarjar.
-rw-r--r-- | build.sbt | 1 | ||||
-rwxr-xr-x | build.xml | 40 | ||||
-rw-r--r-- | src/repl-jline/scala/tools/nsc/interpreter/jline/FileBackedHistory.scala (renamed from src/repl/scala/tools/nsc/interpreter/jline/FileBackedHistory.scala) | 0 | ||||
-rw-r--r-- | src/repl-jline/scala/tools/nsc/interpreter/jline/JLineDelimiter.scala (renamed from src/repl/scala/tools/nsc/interpreter/jline/JLineDelimiter.scala) | 0 | ||||
-rw-r--r-- | src/repl-jline/scala/tools/nsc/interpreter/jline/JLineHistory.scala (renamed from src/repl/scala/tools/nsc/interpreter/jline/JLineHistory.scala) | 0 | ||||
-rw-r--r-- | src/repl-jline/scala/tools/nsc/interpreter/jline/JLineReader.scala (renamed from src/repl/scala/tools/nsc/interpreter/jline/JLineReader.scala) | 2 | ||||
-rw-r--r-- | src/repl/scala/tools/nsc/interpreter/ILoop.scala | 40 |
7 files changed, 70 insertions, 13 deletions
@@ -191,6 +191,7 @@ lazy val interactive = configureAsSubproject(project) .settings(disableDocsAndPublishingTasks: _*) .dependsOn(compiler) +// TODO: SI-9339 embed shaded copy of jline & its interface (see #4563) lazy val repl = configureAsSubproject(project) .settings(libraryDependencies += jlineDep) .settings(disableDocsAndPublishingTasks: _*) @@ -275,6 +275,10 @@ TODO: <dependency groupId="biz.aQute" artifactId="bnd" version="1.50.0"/> </artifact:dependencies> + <artifact:dependencies pathId="jarjar.classpath"> + <dependency groupId="com.googlecode.jarjar" artifactId="jarjar" version="1.3"/> + </artifact:dependencies> + <!-- JUnit --> <property name="junit.version" value="4.11"/> <artifact:dependencies pathId="junit.classpath" filesetId="junit.fileset"> @@ -696,7 +700,7 @@ TODO: <property name="partest-javaagent.description" value="Scala Compiler Testing Tool (compiler-specific java agent)"/> <!-- projects without project-specific options: forkjoin, manual, bin, repl --> - <for list="actors,compiler,interactive,scaladoc,library,parser-combinators,partest,partest-extras,partest-javaagent,reflect,scalap,swing,xml,continuations-plugin,continuations-library" param="project"> + <for list="actors,compiler,interactive,scaladoc,library,parser-combinators,partest,partest-extras,partest-javaagent,reflect,scalap,swing,xml,continuations-plugin,continuations-library,repl-jline" param="project"> <sequential> <!-- description is mandatory --> <init-project-prop project="@{project}" name="package" default=""/> <!-- used by mvn-package, copy-bundle, make-bundle --> @@ -799,6 +803,11 @@ TODO: <path id="quick.repl.build.path"> <path refid="quick.compiler.build.path"/> <pathelement location="${build-quick.dir}/classes/repl"/> + </path> + + <path id="quick.repl-jline.build.path"> + <path refid="quick.repl.build.path"/> + <pathelement location="${build-quick.dir}/classes/repl-jline"/> <path refid="repl.deps.classpath"/> </path> @@ -873,6 +882,8 @@ TODO: <fileset dir="${build-quick.dir}/classes/actors"/> </path> + <path id="pack.repl-jline.files"> <fileset dir="${build-quick.dir}/classes/repl-jline"/> </path> + <path id="pack.compiler.files"> <fileset dir="${build-quick.dir}/classes/compiler"/> @@ -1076,6 +1087,7 @@ TODO: </patternset> <taskdef resource="scala/tools/ant/sabbus/antlib.xml" classpathref="starr.compiler.path"/> + <taskdef name="jarjar" classname="com.tonicsystems.jarjar.JarJarTask" classpathref="jarjar.classpath" /> </target> <!-- =========================================================================== @@ -1157,7 +1169,31 @@ TODO: <staged-build with="locker" stage="quick" project="compiler"/> </target> <target name="quick.repl" depends="quick.comp"> - <staged-build with="locker" stage="quick" project="repl"/> </target> + <staged-build with="locker" stage="quick" project="repl"/> + <staged-build with="locker" stage="quick" project="repl-jline"/> + + <staged-pack project="repl-jline"/> + + <!-- make jline_embedded jar with classes of repl-jline and jline, then shade--> + <jarjar jarfile="${build-pack.dir}/${repl-jline.targetdir}/scala-repl-jline-embedded.jar" whenmanifestonly="fail"> + <zipfileset src="${jline:jline:jar}"/> + <zipfileset src="${build-pack.dir}/${repl-jline.targetdir}/${repl-jline.targetjar}"/> + + <rule pattern="org.fusesource.**" result="scala.tools.fusesource_embedded.@1"/> + <rule pattern="jline.**" result="scala.tools.jline_embedded.@1"/> + <rule pattern="scala.tools.nsc.interpreter.jline.**" result="scala.tools.nsc.interpreter.jline_embedded.@1"/> + <keep pattern="scala.tools.**"/> + </jarjar> + + <!-- unzip jar to repl's class dir to obtain + - standard repl-jline + - a shaded repl-jline (scala/tools/nsc/interpreter/jline_embedded) & jline (scala.tools.jline_embedded) + --> + <copy todir="${build-quick.dir}/classes/repl"> + <zipfileset src="${build-pack.dir}/${repl-jline.targetdir}/${repl-jline.targetjar}"/> + <zipfileset src="${build-pack.dir}/${repl-jline.targetdir}/scala-repl-jline-embedded.jar"/> + </copy> + </target> <target name="quick.scaladoc" depends="quick.comp"> <staged-build with="locker" stage="quick" project="scaladoc" version="scaladoc"/> </target> diff --git a/src/repl/scala/tools/nsc/interpreter/jline/FileBackedHistory.scala b/src/repl-jline/scala/tools/nsc/interpreter/jline/FileBackedHistory.scala index b6c9792ec0..b6c9792ec0 100644 --- a/src/repl/scala/tools/nsc/interpreter/jline/FileBackedHistory.scala +++ b/src/repl-jline/scala/tools/nsc/interpreter/jline/FileBackedHistory.scala diff --git a/src/repl/scala/tools/nsc/interpreter/jline/JLineDelimiter.scala b/src/repl-jline/scala/tools/nsc/interpreter/jline/JLineDelimiter.scala index c18a9809a0..c18a9809a0 100644 --- a/src/repl/scala/tools/nsc/interpreter/jline/JLineDelimiter.scala +++ b/src/repl-jline/scala/tools/nsc/interpreter/jline/JLineDelimiter.scala diff --git a/src/repl/scala/tools/nsc/interpreter/jline/JLineHistory.scala b/src/repl-jline/scala/tools/nsc/interpreter/jline/JLineHistory.scala index 1f6a1f7022..1f6a1f7022 100644 --- a/src/repl/scala/tools/nsc/interpreter/jline/JLineHistory.scala +++ b/src/repl-jline/scala/tools/nsc/interpreter/jline/JLineHistory.scala diff --git a/src/repl/scala/tools/nsc/interpreter/jline/JLineReader.scala b/src/repl-jline/scala/tools/nsc/interpreter/jline/JLineReader.scala index 414868a7e5..f0fce13fe8 100644 --- a/src/repl/scala/tools/nsc/interpreter/jline/JLineReader.scala +++ b/src/repl-jline/scala/tools/nsc/interpreter/jline/JLineReader.scala @@ -24,7 +24,7 @@ import scala.tools.nsc.interpreter.session.History * * Eagerly instantiates all relevant JLine classes, so that we can detect linkage errors on `new JLineReader` and retry. */ -class JLineReader(completer: () => Completion) extends interpreter.InteractiveReader { +class InteractiveReader(completer: () => Completion) extends interpreter.InteractiveReader { val interactive = true val history: History = new JLineHistory.JLineFileHistory() diff --git a/src/repl/scala/tools/nsc/interpreter/ILoop.scala b/src/repl/scala/tools/nsc/interpreter/ILoop.scala index 3ce9668b97..a3047ccc8e 100644 --- a/src/repl/scala/tools/nsc/interpreter/ILoop.scala +++ b/src/repl/scala/tools/nsc/interpreter/ILoop.scala @@ -26,6 +26,8 @@ import scala.concurrent.{ ExecutionContext, Await, Future, future } import ExecutionContext.Implicits._ import java.io.{ BufferedReader, FileReader } +import scala.util.{Try, Success, Failure} + /** The Scala interactive shell. It provides a read-eval-print loop * around the Interpreter class. * After instantiation, clients should call the main() method. @@ -860,18 +862,36 @@ class ILoop(in0: Option[BufferedReader], protected val out: JPrintWriter) * with SimpleReader. */ def chooseReader(settings: Settings): InteractiveReader = { - def mkJLineReader(completer: () => Completion): InteractiveReader = - try new jline.JLineReader(completer) - catch { - case ex@(_: Exception | _: NoClassDefFoundError) => - Console.println(f"Failed to created JLineReader: ${ex}%nFalling back to SimpleReader.") - SimpleReader() - } - if (settings.Xnojline || Properties.isEmacsShell) SimpleReader() else { - if (settings.noCompletion) mkJLineReader(() => NoCompletion) - else mkJLineReader(() => new JLineCompletion(intp)) + type Completer = () => Completion + type ReaderMaker = Completer => InteractiveReader + + def instantiate(className: String): ReaderMaker = completer => { + if (settings.debug) Console.println(s"Trying to instantiate a InteractiveReader from $className") + Class.forName(className).getConstructor(classOf[Completer]). + newInstance(completer). + asInstanceOf[InteractiveReader] + } + + def mkReader(maker: ReaderMaker) = + if (settings.noCompletion) maker(() => NoCompletion) + else maker(() => new JLineCompletion(intp)) // JLineCompletion is a misnomer -- it's not tied to jline + + def internalClass(kind: String) = s"scala.tools.nsc.interpreter.$kind.InteractiveReader" + val readerClasses = sys.props.get("scala.repl.reader").toStream ++ Stream(internalClass("jline"), internalClass("jline_embedded")) + val readers = readerClasses map (cls => Try { mkReader(instantiate(cls)) }) + + val reader = (readers collect { case Success(reader) => reader } headOption) getOrElse SimpleReader() + + if (settings.debug) { + val readerDiags = (readerClasses, readers).zipped map { + case (cls, Failure(e)) => s" - $cls --> " + e.getStackTrace.mkString(e.toString+"\n\t", "\n\t","\n") + case (cls, Success(_)) => s" - $cls OK" + } + Console.println(s"All InteractiveReaders tried: ${readerDiags.mkString("\n","\n","\n")}") + } + reader } } |