1+ package xsbt ;
2+
3+ import java .lang .reflect .Field ;
4+
5+ import java .net .URL ;
6+ import java .net .URLClassLoader ;
7+
8+ import java .util .WeakHashMap ;
9+
10+ /**
11+ * A classloader to run the compiler
12+ * <p>
13+ * A CompilerClassLoader is constructed from a list of `urls` that need to be on
14+ * the classpath to run the compiler and the classloader used by sbt.
15+ * <p>
16+ * To understand why a custom classloader is needed for the compiler, let us
17+ * describe some alternatives that wouldn't work.
18+ * <ul>
19+ * <li>`new URLClassLoader(urls)`:
20+ * The compiler contains sbt phases that callback to sbt using the `xsbti.*`
21+ * interfaces. If `urls` does not contain the sbt interfaces we'll get a
22+ * `ClassNotFoundException` in the compiler when we try to use them, if
23+ * `urls` does contain the interfaces we'll get a `ClassCastException` or a
24+ * `LinkageError` because if the same class is loaded by two different
25+ * classloaders, they are considered distinct by the JVM.
26+ * <li>`new URLClassLoader(urls, sbtLoader)`:
27+ * Because of the JVM delegation model, this means that we will only load
28+ * a class from `urls` if it's not present in the parent `sbtLoader`, but
29+ * sbt uses its own version of the scala compiler and scala library which
30+ * is not the one we need to run the compiler.
31+ * </ul>
32+ * <p>
33+ * Our solution is to implement a subclass of URLClassLoader with no parent, instead
34+ * we override `loadClass` to load the `xsbti.*` interfaces from `sbtLoader`.
35+ */
36+ public class CompilerClassLoader extends URLClassLoader {
37+ private final ClassLoader sbtLoader ;
38+
39+ public CompilerClassLoader (URL [] urls , ClassLoader sbtLoader ) {
40+ super (urls , null );
41+ this .sbtLoader = sbtLoader ;
42+ }
43+
44+ @ Override
45+ public Class <?> loadClass (String className , boolean resolve ) throws ClassNotFoundException {
46+ if (className .startsWith ("xsbti." )) {
47+ // We can't use the loadClass overload with two arguments because it's
48+ // protected, but we can do the same by hand (the classloader instance
49+ // from which we call resolveClass does not matter).
50+ Class <?> c = sbtLoader .loadClass (className );
51+ if (resolve )
52+ resolveClass (c );
53+ return c ;
54+ } else {
55+ return super .loadClass (className , resolve );
56+ }
57+ }
58+
59+ /**
60+ * Cache the result of `fixBridgeLoader`.
61+ * <p>
62+ * Reusing ClassLoaders is important for warm performance since otherwise the
63+ * JIT code cache for the compiler will be discarded between every call to
64+ * the sbt `compile` task.
65+ */
66+ private static WeakHashMap <ClassLoader , ClassLoader > fixedLoaderCache = new WeakHashMap <>();
67+
68+ /**
69+ * Fix the compiler bridge ClassLoader
70+ * <p>
71+ * Soundtrack: https://www.youtube.com/watch?v=imamcajBEJs
72+ * <p>
73+ * The classloader that we get from sbt looks like:
74+ * <p>
75+ * URLClassLoader(bridgeURLs,
76+ * DualLoader(scalaLoader, notXsbtiFilter, sbtLoader, xsbtiFilter))
77+ * <p>
78+ * DualLoader will load the `xsbti.*` interfaces using `sbtLoader` and
79+ * everything else with `scalaLoader`. Once we have loaded the dotty Main
80+ * class using `scalaLoader`, subsequent classes in the dotty compiler will
81+ * also be loaded by `scalaLoader` and _not_ by the DualLoader. But the sbt
82+ * compiler phases are part of dotty and still need access to the `xsbti.*`
83+ * interfaces in `sbtLoader`, therefore DualLoader does not work for us
84+ * (this issue is not present with scalac because the sbt phases are
85+ * currently defined in the compiler bridge itself, not in scalac).
86+ * <p>
87+ * CompilerClassLoader is a replacement for DualLoader. Until we can fix
88+ * this in sbt proper, we need to use reflection to construct our own
89+ * fixed classloader:
90+ * <p>
91+ * URLClassLoader(bridgeURLs,
92+ * CompilerClassLoader(scalaLoader.getURLs, sbtLoader))
93+ *
94+ * @param bridgeLoader The classloader that sbt uses to load the compiler bridge
95+ * @return A fixed classloader that works with dotty
96+ */
97+ synchronized public static ClassLoader fixBridgeLoader (ClassLoader bridgeLoader ) {
98+ return fixedLoaderCache .computeIfAbsent (bridgeLoader , k -> computeFixedLoader (k ));
99+ }
100+
101+ private static ClassLoader computeFixedLoader (ClassLoader bridgeLoader ) {
102+ URLClassLoader urlBridgeLoader = (URLClassLoader ) bridgeLoader ;
103+ ClassLoader dualLoader = urlBridgeLoader .getParent ();
104+ Class <?> dualLoaderClass = dualLoader .getClass ();
105+
106+ try {
107+ // DualLoader.parentA and DualLoader.parentB are private
108+ Field parentAField = dualLoaderClass .getDeclaredField ("parentA" );
109+ parentAField .setAccessible (true );
110+ Field parentBField = dualLoaderClass .getDeclaredField ("parentB" );
111+ parentBField .setAccessible (true );
112+ URLClassLoader scalaLoader = (URLClassLoader ) parentAField .get (dualLoader );
113+ ClassLoader sbtLoader = (ClassLoader ) parentBField .get (dualLoader );
114+
115+ URL [] bridgeURLs = urlBridgeLoader .getURLs ();
116+ return new URLClassLoader (bridgeURLs ,
117+ new CompilerClassLoader (scalaLoader .getURLs (), sbtLoader ));
118+ } catch (NoSuchFieldException | IllegalAccessException e ) {
119+ throw new RuntimeException (e );
120+ }
121+ }
122+ }
0 commit comments