aboutsummaryrefslogtreecommitdiff
path: root/sbt-bridge/src/xsbt/CompilerClassLoader.scala
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/CompilerClassLoader.scala
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/CompilerClassLoader.scala')
-rw-r--r--sbt-bridge/src/xsbt/CompilerClassLoader.scala90
1 files changed, 90 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))
+ }
+}