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