aboutsummaryrefslogtreecommitdiff
path: root/sbt-bridge/src/xsbt
diff options
context:
space:
mode:
authorGuillaume Martres <smarter@ubuntu.com>2016-11-20 00:02:50 +0100
committerGuillaume Martres <smarter@ubuntu.com>2016-11-22 01:35:08 +0100
commitc3eb841ce8ae349d9820dbf6c18884955e74254e (patch)
tree5e82e22a6f0e8245c11a6db81cb9647106a14bde /sbt-bridge/src/xsbt
parentda1bfe392c638fc03181e0d6b51eb41dbdcce548 (diff)
downloaddotty-c3eb841ce8ae349d9820dbf6c18884955e74254e.tar.gz
dotty-c3eb841ce8ae349d9820dbf6c18884955e74254e.tar.bz2
dotty-c3eb841ce8ae349d9820dbf6c18884955e74254e.zip
Make every project use the new directory structure
Diffstat (limited to 'sbt-bridge/src/xsbt')
-rw-r--r--sbt-bridge/src/xsbt/CompilerClassLoader.scala90
-rw-r--r--sbt-bridge/src/xsbt/CompilerInterface.scala72
-rw-r--r--sbt-bridge/src/xsbt/ConsoleInterface.scala73
-rw-r--r--sbt-bridge/src/xsbt/DelegatingReporter.scala48
-rw-r--r--sbt-bridge/src/xsbt/Log.scala10
-rw-r--r--sbt-bridge/src/xsbt/Message.scala8
-rw-r--r--sbt-bridge/src/xsbt/ScaladocInterface.scala72
7 files changed, 373 insertions, 0 deletions
diff --git a/sbt-bridge/src/xsbt/CompilerClassLoader.scala b/sbt-bridge/src/xsbt/CompilerClassLoader.scala
new file mode 100644
index 000000000..3cb3f344f
--- /dev/null
+++ b/sbt-bridge/src/xsbt/CompilerClassLoader.scala
@@ -0,0 +1,90 @@
+package xsbt
+
+import java.net.{URL, URLClassLoader}
+
+/** A classloader to run the compiler
+ *
+ * A CompilerClassLoader is constructed from a list of `urls` that need to be on
+ * the classpath to run the compiler and the classloader used by sbt.
+ *
+ * To understand why a custom classloader is needed for the compiler, let us
+ * describe some alternatives that wouldn't work.
+ * - `new URLClassLoader(urls)`:
+ * The compiler contains sbt phases that callback to sbt using the `xsbti.*`
+ * interfaces. If `urls` does not contain the sbt interfaces we'll get a
+ * `ClassNotFoundException` in the compiler when we try to use them, if
+ * `urls` does contain the interfaces we'll get a `ClassCastException` or a
+ * `LinkageError` because if the same class is loaded by two different
+ * classloaders, they are considered distinct by the JVM.
+ * - `new URLClassLoader(urls, sbtLoader)`:
+ * Because of the JVM delegation model, this means that we will only load
+ * a class from `urls` if it's not present in the parent `sbtLoader`, but
+ * sbt uses its own version of the scala compiler and scala library which
+ * is not the one we need to run the compiler.
+ *
+ * Our solution is to implement a subclass of URLClassLoader with no parent, instead
+ * we override `loadClass` to load the `xsbti.*` interfaces from `sbtLoader`.
+ */
+class CompilerClassLoader(urls: Array[URL], sbtLoader: ClassLoader)
+ extends URLClassLoader(urls, null) {
+ override def loadClass(className: String, resolve: Boolean): Class[_] =
+ if (className.startsWith("xsbti.")) {
+ // We can't use the loadClass overload with two arguments because it's
+ // protected, but we can do the same by hand (the classloader instance
+ // from which we call resolveClass does not matter).
+ val c = sbtLoader.loadClass(className)
+ if (resolve)
+ resolveClass(c)
+ c
+ } else {
+ super.loadClass(className, resolve)
+ }
+}
+
+object CompilerClassLoader {
+ /** Fix the compiler bridge ClassLoader
+ *
+ * Soundtrack: https://www.youtube.com/watch?v=imamcajBEJs
+ *
+ * The classloader that we get from sbt looks like:
+ *
+ * URLClassLoader(bridgeURLs,
+ * DualLoader(scalaLoader, notXsbtiFilter, sbtLoader, xsbtiFilter))
+ *
+ * DualLoader will load the `xsbti.*` interfaces using `sbtLoader` and
+ * everything else with `scalaLoader`. Once we have loaded the dotty Main
+ * class using `scalaLoader`, subsequent classes in the dotty compiler will
+ * also be loaded by `scalaLoader` and _not_ by the DualLoader. But the sbt
+ * compiler phases are part of dotty and still need access to the `xsbti.*`
+ * interfaces in `sbtLoader`, therefore DualLoader does not work for us
+ * (this issue is not present with scalac because the sbt phases are
+ * currently defined in the compiler bridge itself, not in scalac).
+ *
+ * CompilerClassLoader is a replacement for DualLoader. Until we can fix
+ * this in sbt proper, we need to use reflection to construct our own
+ * fixed classloader:
+ *
+ * URLClassLoader(bridgeURLs,
+ * CompilerClassLoader(scalaLoader.getURLs, sbtLoader))
+ *
+ * @param bridgeLoader The classloader that sbt uses to load the compiler bridge
+ * @return A fixed classloader that works with dotty
+ */
+ def fixBridgeLoader(bridgeLoader: ClassLoader) = bridgeLoader match {
+ case bridgeLoader: URLClassLoader =>
+ val dualLoader = bridgeLoader.getParent
+ val dualLoaderClass = dualLoader.getClass
+
+ // DualLoader#parentA and DualLoader#parentB are private
+ val parentAField = dualLoaderClass.getDeclaredField("parentA")
+ parentAField.setAccessible(true)
+ val parentBField = dualLoaderClass.getDeclaredField("parentB")
+ parentBField.setAccessible(true)
+ val scalaLoader = parentAField.get(dualLoader).asInstanceOf[URLClassLoader]
+ val sbtLoader = parentBField.get(dualLoader).asInstanceOf[URLClassLoader]
+
+ val bridgeURLs = bridgeLoader.getURLs
+ new URLClassLoader(bridgeURLs,
+ new CompilerClassLoader(scalaLoader.getURLs, sbtLoader))
+ }
+}
diff --git a/sbt-bridge/src/xsbt/CompilerInterface.scala b/sbt-bridge/src/xsbt/CompilerInterface.scala
new file mode 100644
index 000000000..bf1488dad
--- /dev/null
+++ b/sbt-bridge/src/xsbt/CompilerInterface.scala
@@ -0,0 +1,72 @@
+/* sbt -- Simple Build Tool
+ * Copyright 2008, 2009 Mark Harrah
+ */
+package xsbt
+
+import xsbti.{ AnalysisCallback, Logger, Problem, Reporter, Severity, DependencyContext }
+import xsbti.api.SourceAPI
+import xsbti.compile._
+import Log.debug
+import java.io.File
+
+import dotty.tools.dotc.core.Contexts.ContextBase
+import dotty.tools.dotc.{ Main => DottyMain }
+import dotty.tools.dotc.interfaces._
+
+import java.net.URLClassLoader
+
+final class CompilerInterface {
+ def newCompiler(options: Array[String], output: Output, initialLog: Logger,
+ initialDelegate: Reporter, resident: Boolean): CachedCompiler = {
+ // The classloader that sbt uses to load the compiler bridge is broken
+ // (see CompilerClassLoader#fixBridgeLoader for details). To workaround
+ // this we construct our own ClassLoader and then run the following code
+ // with it:
+ // new CachedCompilerImpl(options, output, resident)
+
+ val bridgeLoader = getClass.getClassLoader
+ val fixedLoader = CompilerClassLoader.fixBridgeLoader(bridgeLoader)
+ val cciClass = fixedLoader.loadClass("xsbt.CachedCompilerImpl")
+ cciClass.getConstructors.head
+ .newInstance(options, output, resident: java.lang.Boolean)
+ .asInstanceOf[CachedCompiler]
+ }
+
+ def run(sources: Array[File], changes: DependencyChanges, callback: AnalysisCallback, log: Logger,
+ delegate: Reporter, progress: CompileProgress, cached: CachedCompiler): Unit =
+ cached.run(sources, changes, callback, log, delegate, progress)
+}
+
+class CachedCompilerImpl(args: Array[String], output: Output, resident: Boolean) extends CachedCompiler {
+ val outputArgs =
+ output match {
+ case multi: MultipleOutput =>
+ ???
+ case single: SingleOutput =>
+ List("-d", single.outputDirectory.getAbsolutePath.toString)
+ }
+
+ def commandArguments(sources: Array[File]): Array[String] =
+ (outputArgs ++ args.toList ++ sources.map(_.getAbsolutePath).sortWith(_ < _)).toArray[String]
+
+ def run(sources: Array[File], changes: DependencyChanges, callback: AnalysisCallback, log: Logger, delegate: Reporter, progress: CompileProgress): Unit = synchronized {
+ run(sources.toList, changes, callback, log, delegate, progress)
+ }
+ private[this] def run(sources: List[File], changes: DependencyChanges, callback: AnalysisCallback, log: Logger, delegate: Reporter, compileProgress: CompileProgress): Unit = {
+ debug(log, args.mkString("Calling Dotty compiler with arguments (CompilerInterface):\n\t", "\n\t", ""))
+ val ctx = (new ContextBase).initialCtx.fresh
+ .setSbtCallback(callback)
+ .setReporter(new DelegatingReporter(delegate))
+
+ val cl = getClass.getClassLoader.asInstanceOf[URLClassLoader]
+
+ val reporter = DottyMain.process(commandArguments(sources.toArray), ctx)
+ if (reporter.hasErrors) {
+ throw new InterfaceCompileFailed(args, Array())
+ }
+ }
+}
+
+class InterfaceCompileFailed(override val arguments: Array[String], override val problems: Array[Problem]) extends xsbti.CompileFailed {
+ override val toString = "Compilation failed"
+}
diff --git a/sbt-bridge/src/xsbt/ConsoleInterface.scala b/sbt-bridge/src/xsbt/ConsoleInterface.scala
new file mode 100644
index 000000000..f56918113
--- /dev/null
+++ b/sbt-bridge/src/xsbt/ConsoleInterface.scala
@@ -0,0 +1,73 @@
+/* sbt -- Simple Build Tool
+ * Copyright 2008, 2009 Mark Harrah
+ */
+package xsbt
+
+import xsbti.Logger
+import scala.tools.nsc.{ GenericRunnerCommand, Interpreter, InterpreterLoop, ObjectRunner, Settings }
+import scala.tools.nsc.interpreter.InteractiveReader
+import scala.tools.nsc.reporters.Reporter
+import scala.tools.nsc.util.ClassPath
+
+import dotty.tools.dotc.core.Contexts.Context
+import dotty.tools.dotc.repl.REPL
+import dotty.tools.dotc.repl.REPL.Config
+
+class ConsoleInterface {
+ def commandArguments(
+ args: Array[String],
+ bootClasspathString: String,
+ classpathString: String,
+ log: Logger
+ ): Array[String] = args
+
+ def run(args: Array[String],
+ bootClasspathString: String,
+ classpathString: String,
+ initialCommands: String,
+ cleanupCommands: String,
+ loader: ClassLoader,
+ bindNames: Array[String],
+ bindValues: Array[Any],
+ log: Logger
+ ): Unit = {
+ val completeArgs =
+ args :+
+ "-bootclasspath" :+ bootClasspathString :+
+ "-classpath" :+ classpathString
+
+ println("Starting dotty interpreter...")
+ val repl = ConsoleInterface.customRepl(
+ initialCommands :: Nil,
+ cleanupCommands :: Nil,
+ bindNames zip bindValues,
+ loader
+ )
+ repl.process(completeArgs)
+ }
+}
+
+object ConsoleInterface {
+ def customConfig(
+ initCmds: List[String],
+ cleanupCmds: List[String],
+ boundVals: Array[(String, Any)],
+ loader: ClassLoader
+ ) = new Config {
+ override val initialCommands: List[String] = initCmds
+ override val cleanupCommands: List[String] = cleanupCmds
+ override val boundValues: Array[(String, Any)] = boundVals
+ override val classLoader: Option[ClassLoader] = Option(loader)
+ }
+
+ def customRepl(cfg: Config): REPL = new REPL {
+ override lazy val config = cfg
+ }
+
+ def customRepl(
+ initCmds: List[String],
+ cleanupCmds: List[String],
+ boundVals: Array[(String, Any)],
+ loader: ClassLoader
+ ): REPL = customRepl(customConfig(initCmds, cleanupCmds, boundVals, loader))
+}
diff --git a/sbt-bridge/src/xsbt/DelegatingReporter.scala b/sbt-bridge/src/xsbt/DelegatingReporter.scala
new file mode 100644
index 000000000..770d6b2c7
--- /dev/null
+++ b/sbt-bridge/src/xsbt/DelegatingReporter.scala
@@ -0,0 +1,48 @@
+/* sbt -- Simple Build Tool
+ * Copyright 2008, 2009 Mark Harrah
+ */
+package xsbt
+
+import dotty.tools._
+import dotc._
+import reporting._
+import reporting.diagnostic.MessageContainer
+import reporting.diagnostic.messages
+import core.Contexts._
+import xsbti.{Maybe, Position}
+
+final class DelegatingReporter(delegate: xsbti.Reporter) extends Reporter
+ with UniqueMessagePositions
+ with HideNonSensicalMessages
+ with MessageRendering {
+ import MessageContainer._
+
+ override def printSummary(implicit ctx: Context): Unit = delegate.printSummary()
+
+ def doReport(cont: MessageContainer)(implicit ctx: Context): Unit = {
+ val severity =
+ cont match {
+ case _: messages.Error => xsbti.Severity.Error
+ case _: messages.Warning => xsbti.Severity.Warn
+ case _ => xsbti.Severity.Info
+ }
+
+ val position = new Position {
+ def line: Maybe[Integer] = Maybe.nothing()
+ def lineContent: String = ""
+ def offset: Maybe[Integer] = Maybe.nothing()
+ def pointer: Maybe[Integer] = Maybe.nothing()
+ def pointerSpace: Maybe[String] = Maybe.nothing()
+ def sourceFile: Maybe[java.io.File] = Maybe.nothing()
+ def sourcePath: Maybe[String] = Maybe.nothing()
+ }
+
+ val sb = new StringBuilder()
+ sb.append(messageAndPos(cont.contained, cont.pos, diagnosticLevel(cont)))
+ if (ctx.shouldExplain(cont) && cont.contained.explanation.nonEmpty) {
+ sb.append(explanation(cont.contained))
+ }
+
+ delegate.log(position, sb.toString(), severity)
+ }
+}
diff --git a/sbt-bridge/src/xsbt/Log.scala b/sbt-bridge/src/xsbt/Log.scala
new file mode 100644
index 000000000..e514d7abb
--- /dev/null
+++ b/sbt-bridge/src/xsbt/Log.scala
@@ -0,0 +1,10 @@
+/* sbt -- Simple Build Tool
+ * Copyright 2008, 2009 Mark Harrah
+ */
+package xsbt
+
+object Log {
+ def debug(log: xsbti.Logger, msg: => String) = log.debug(Message(msg))
+ def settingsError(log: xsbti.Logger): String => Unit =
+ s => log.error(Message(s))
+}
diff --git a/sbt-bridge/src/xsbt/Message.scala b/sbt-bridge/src/xsbt/Message.scala
new file mode 100644
index 000000000..48f24f533
--- /dev/null
+++ b/sbt-bridge/src/xsbt/Message.scala
@@ -0,0 +1,8 @@
+/* sbt -- Simple Build Tool
+ * Copyright 2008, 2009 Mark Harrah
+ */
+package xsbt
+
+object Message {
+ def apply[T](s: => T) = new xsbti.F0[T] { def apply() = s }
+}
diff --git a/sbt-bridge/src/xsbt/ScaladocInterface.scala b/sbt-bridge/src/xsbt/ScaladocInterface.scala
new file mode 100644
index 000000000..3ad9c7941
--- /dev/null
+++ b/sbt-bridge/src/xsbt/ScaladocInterface.scala
@@ -0,0 +1,72 @@
+/* sbt -- Simple Build Tool
+ * Copyright 2008, 2009 Mark Harrah
+ */
+package xsbt
+
+import xsbti.Logger
+import dotty.tools.dottydoc.api.scala.Dottydoc
+import java.net.URL
+
+class ScaladocInterface {
+ def run(args: Array[String], log: Logger, delegate: xsbti.Reporter) =
+ (new DottydocRunner(args, log, delegate)).run()
+}
+
+class DottydocRunner(args: Array[String], log: Logger, delegate: xsbti.Reporter) extends Dottydoc {
+ def run(): Unit = getOutputFolder(args).map { outputFolder =>
+ val index = createIndex(args)
+ val resources = getResources(args)
+ val template = getTemplate(resources)
+
+ template.fold(writeJson(index, outputFolder)) { tpl =>
+ buildDocs(outputFolder, tpl, resources, index)
+ }
+ } getOrElse {
+ delegate.log(
+ NoPosition,
+ "No output folder set for API documentation (\"-d\" parameter should be passed to the documentation tool)",
+ xsbti.Severity.Error
+ )
+ }
+
+ private[this] val NoPosition = new xsbti.Position {
+ val line = xsbti.Maybe.nothing[Integer]
+ val lineContent = ""
+ val offset = xsbti.Maybe.nothing[Integer]
+ val sourcePath = xsbti.Maybe.nothing[String]
+ val sourceFile = xsbti.Maybe.nothing[java.io.File]
+ val pointer = xsbti.Maybe.nothing[Integer]
+ val pointerSpace = xsbti.Maybe.nothing[String]
+ }
+
+ private def getStringSetting(name: String): Option[String] =
+ args find (_.startsWith(name)) map (_.drop(name.length))
+
+ private def getOutputFolder(args: Array[String]): Option[String] =
+ args sliding(2) find { case Array(x, _) => x == "-d" } map (_.tail.head.trim)
+
+ private def getTemplate(resources: List[URL]): Option[URL] =
+ resources.find(_.getFile.endsWith("template.html"))
+
+ private def getResources(args: Array[String]): List[URL] = {
+ val cp = args sliding (2) find { case Array(x, _) => x == "-classpath" } map (_.tail.head.trim) getOrElse ""
+
+ cp.split(":").find(_.endsWith("dottydoc-client.jar")).map { resourceJar =>
+ import java.util.jar.JarFile
+ val jarEntries = (new JarFile(resourceJar)).entries
+ var entries: List[URL] = Nil
+
+ while (jarEntries.hasMoreElements) {
+ val entry = jarEntries.nextElement()
+
+ if (!entry.isDirectory()) {
+ val path = s"jar:file:$resourceJar!/${entry.getName}"
+ val url = new URL(path)
+ entries = url :: entries
+ }
+ }
+
+ entries
+ } getOrElse (Nil)
+ }
+}