@@ -8,11 +8,16 @@ import sbt.librarymanagement.{
8
8
VersionNumber
9
9
}
10
10
import sbt .internal .inc .ScalaInstance
11
+ import sbt .internal .inc .classpath .ClassLoaderCache
11
12
import xsbti .compile ._
13
+ import xsbti .AppConfiguration
12
14
import java .net .URLClassLoader
13
15
import java .util .Optional
16
+ import java .util .{Enumeration , Collections }
17
+ import java .net .URL
14
18
import scala .util .Properties .isJavaAtLeast
15
19
20
+
16
21
object DottyPlugin extends AutoPlugin {
17
22
object autoImport {
18
23
val isDotty = settingKey[Boolean ](" Is this project compiled with Dotty?" )
@@ -524,15 +529,34 @@ object DottyPlugin extends AutoPlugin {
524
529
scalaLibraryJar,
525
530
dottyLibraryJar,
526
531
compilerJar,
527
- allJars
532
+ allJars,
533
+ appConfiguration.value
528
534
)
529
535
}
530
536
531
537
// Adapted from private mkScalaInstance in sbt
532
538
def makeScalaInstance (
533
- state : State , dottyVersion : String , scalaLibrary : File , dottyLibrary : File , compiler : File , all : Seq [File ]
539
+ state : State , dottyVersion : String , scalaLibrary : File , dottyLibrary : File , compiler : File , all : Seq [File ], appConfiguration : AppConfiguration
534
540
): ScalaInstance = {
535
- val libraryLoader = state.classLoaderCache(List (dottyLibrary, scalaLibrary))
541
+ /**
542
+ * The compiler bridge must load the xsbti classes from the sbt
543
+ * classloader, and similarly the Scala repl must load the sbt provided
544
+ * jline terminal. To do so we add the `appConfiguration` loader in
545
+ * the parent hierarchy of the scala 3 instance loader.
546
+ *
547
+ * The [[TopClassLoader ]] ensures that the xsbti and jline classes
548
+ * only are loaded from the sbt loader. That is necessary because
549
+ * the sbt class loader contains the Scala 2.12 library and compiler
550
+ * bridge.
551
+ */
552
+ val topLoader = new TopClassLoader (appConfiguration.provider.loader)
553
+
554
+ val libraryJars = Array (dottyLibrary, scalaLibrary)
555
+ val libraryLoader = state.classLoaderCache.cachedCustomClassloader(
556
+ libraryJars.toList,
557
+ () => new URLClassLoader (libraryJars.map(_.toURI.toURL), topLoader)
558
+ )
559
+
536
560
class DottyLoader
537
561
extends URLClassLoader (all.map(_.toURI.toURL).toArray, libraryLoader)
538
562
val fullLoader = state.classLoaderCache.cachedCustomClassloader(
@@ -543,10 +567,53 @@ object DottyPlugin extends AutoPlugin {
543
567
dottyVersion,
544
568
fullLoader,
545
569
libraryLoader,
546
- Array (dottyLibrary, scalaLibrary) ,
570
+ libraryJars ,
547
571
compiler,
548
572
all.toArray,
549
573
None )
574
+ }
575
+ }
550
576
577
+ /**
578
+ * The parent classloader of the Scala compiler.
579
+ *
580
+ * A TopClassLoader is constructed from the sbt classloader.
581
+ *
582
+ * To understand why a custom parent classloader is needed for the compiler,
583
+ * let us describe some alternatives that wouldn't work.
584
+ *
585
+ * - `new URLClassLoader(urls)`:
586
+ * The compiler contains sbt phases that callback to sbt using the `xsbti.*`
587
+ * interfaces. If `urls` does not contain the sbt interfaces we'll get a
588
+ * `ClassNotFoundException` in the compiler when we try to use them, if
589
+ * `urls` does contain the interfaces we'll get a `ClassCastException` or a
590
+ * `LinkageError` because if the same class is loaded by two different
591
+ * classloaders, they are considered distinct by the JVM.
592
+ *
593
+ * - `new URLClassLoader(urls, sbtLoader)`:
594
+ * Because of the JVM delegation model, this means that we will only load
595
+ * a class from `urls` if it's not present in the parent `sbtLoader`, but
596
+ * sbt uses its own version of the scala compiler and scala library which
597
+ * is not the one we need to run the compiler.
598
+ *
599
+ * Our solution is to implement an URLClassLoader whose parent is
600
+ * `new TopClassLoader(sbtLoader)`. We override `loadClass` to load the
601
+ * `xsbti.*` interfaces from `sbtLoader`.
602
+ *
603
+ * The parent loader of the TopClassLoader is set to `null` so that the JDK
604
+ * classes and only the JDK classes are loade from it.
605
+ */
606
+ private class TopClassLoader (sbtLoader : ClassLoader ) extends ClassLoader (null ) {
607
+ // We can't use the loadClass overload with two arguments because it's
608
+ // protected, but we can do the same by hand (the classloader instance
609
+ // from which we call resolveClass does not matter).
610
+ // The one argument overload of loadClass delegates to this one.
611
+ override protected def loadClass (name : String , resolve : Boolean ): Class [_] = {
612
+ if (name.startsWith(" xsbti." ) || name.startsWith(" org.jline." )) {
613
+ val c = sbtLoader.loadClass(name)
614
+ if (resolve) resolveClass(c)
615
+ c
616
+ }
617
+ else super .loadClass(name, resolve)
551
618
}
552
619
}
0 commit comments