All Downloads are FREE. Search and download functionalities are using the official Maven repository.

xsbt.CompilerClassLoader Maven / Gradle / Ivy

There is a newer version: 0.28.0-bin-20201014-ca67e4d-NIGHTLY
Show newest version
package xsbt;

import java.lang.reflect.Field;

import java.net.URL;
import java.net.URLClassLoader;

import java.util.WeakHashMap;

/**
 * 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`. */ public class CompilerClassLoader extends URLClassLoader { private final ClassLoader sbtLoader; public CompilerClassLoader(URL[] urls, ClassLoader sbtLoader) { super(urls, null); this.sbtLoader = sbtLoader; } @Override public Class loadClass(String className, boolean resolve) throws ClassNotFoundException { 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). Class c = sbtLoader.loadClass(className); if (resolve) resolveClass(c); return c; } else { return super.loadClass(className, resolve); } } /** * Cache the result of `fixBridgeLoader`. *

* Reusing ClassLoaders is important for warm performance since otherwise the * JIT code cache for the compiler will be discarded between every call to * the sbt `compile` task. */ private static WeakHashMap fixedLoaderCache = new WeakHashMap<>(); /** * 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 */ synchronized public static ClassLoader fixBridgeLoader(ClassLoader bridgeLoader) { return fixedLoaderCache.computeIfAbsent(bridgeLoader, k -> computeFixedLoader(k)); } private static ClassLoader computeFixedLoader(ClassLoader bridgeLoader) { URLClassLoader urlBridgeLoader = (URLClassLoader) bridgeLoader; ClassLoader dualLoader = urlBridgeLoader.getParent(); Class dualLoaderClass = dualLoader.getClass(); try { // DualLoader.parentA and DualLoader.parentB are private Field parentAField = dualLoaderClass.getDeclaredField("parentA"); parentAField.setAccessible(true); Field parentBField = dualLoaderClass.getDeclaredField("parentB"); parentBField.setAccessible(true); URLClassLoader scalaLoader = (URLClassLoader) parentAField.get(dualLoader); URLClassLoader sbtLoader = (URLClassLoader) parentBField.get(dualLoader); URL[] bridgeURLs = urlBridgeLoader.getURLs(); return new URLClassLoader(bridgeURLs, new CompilerClassLoader(scalaLoader.getURLs(), sbtLoader)); } catch (NoSuchFieldException | IllegalAccessException e) { throw new RuntimeException(e); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy