aboutsummaryrefslogtreecommitdiff
path: root/bridge/src/main/scala/xsbt
diff options
context:
space:
mode:
authorGuillaume Martres <smarter@ubuntu.com>2016-05-31 15:23:04 +0200
committerFelix Mulder <felix.mulder@gmail.com>2016-06-07 17:10:36 +0200
commit09b2f39731386cd5b3688d5c3badf75d956d4f6d (patch)
treed122fc0e96d6c1cd22d7e0d0b5ee523563d534e7 /bridge/src/main/scala/xsbt
parent030ff82070197f0c126f5c0287e076b0f6b6dd8d (diff)
downloaddotty-09b2f39731386cd5b3688d5c3badf75d956d4f6d.tar.gz
dotty-09b2f39731386cd5b3688d5c3badf75d956d4f6d.tar.bz2
dotty-09b2f39731386cd5b3688d5c3badf75d956d4f6d.zip
Make the dotty-bridge sbt project a subproject of dotty
Note that the dotty-bridge tests will not be run automatically by `test` which is short for `dotty/test`, to run the dotty-bridge tests, do in sbt: > dotty-bridge/test > dotty-bridge/scripted Original history: https://github.com/smarter/dotty-bridge/commits/master
Diffstat (limited to 'bridge/src/main/scala/xsbt')
-rw-r--r--bridge/src/main/scala/xsbt/CompilerClassLoader.scala90
-rw-r--r--bridge/src/main/scala/xsbt/CompilerInterface.scala70
-rw-r--r--bridge/src/main/scala/xsbt/Log.scala10
-rw-r--r--bridge/src/main/scala/xsbt/Message.scala8
4 files changed, 178 insertions, 0 deletions
diff --git a/bridge/src/main/scala/xsbt/CompilerClassLoader.scala b/bridge/src/main/scala/xsbt/CompilerClassLoader.scala
new file mode 100644
index 000000000..3cb3f344f
--- /dev/null
+++ b/bridge/src/main/scala/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/bridge/src/main/scala/xsbt/CompilerInterface.scala b/bridge/src/main/scala/xsbt/CompilerInterface.scala
new file mode 100644
index 000000000..ee272b8b1
--- /dev/null
+++ b/bridge/src/main/scala/xsbt/CompilerInterface.scala
@@ -0,0 +1,70 @@
+/* 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, progress)
+ }
+ private[this] def run(sources: List[File], changes: DependencyChanges, callback: AnalysisCallback, log: Logger, 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)
+ 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/bridge/src/main/scala/xsbt/Log.scala b/bridge/src/main/scala/xsbt/Log.scala
new file mode 100644
index 000000000..e514d7abb
--- /dev/null
+++ b/bridge/src/main/scala/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/bridge/src/main/scala/xsbt/Message.scala b/bridge/src/main/scala/xsbt/Message.scala
new file mode 100644
index 000000000..48f24f533
--- /dev/null
+++ b/bridge/src/main/scala/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 }
+}