diff options
author | Guillaume Martres <smarter@ubuntu.com> | 2016-11-20 00:02:50 +0100 |
---|---|---|
committer | Guillaume Martres <smarter@ubuntu.com> | 2016-11-22 01:35:08 +0100 |
commit | c3eb841ce8ae349d9820dbf6c18884955e74254e (patch) | |
tree | 5e82e22a6f0e8245c11a6db81cb9647106a14bde /sbt-bridge/src/xsbt/CompilerClassLoader.scala | |
parent | da1bfe392c638fc03181e0d6b51eb41dbdcce548 (diff) | |
download | dotty-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.scala | 90 |
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)) + } +} |