Skip to content

Commit 002ed8e

Browse files
committed
Merge pull request #1306 from dotty-staging/add/dotty-bridge
Merge the sbt compiler bridge as a subproject of dotty
2 parents 9baa972 + 09b2f39 commit 002ed8e

File tree

488 files changed

+4254
-1
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

488 files changed

+4254
-1
lines changed

AUTHORS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ The majority of the dotty codebase is new code, with the exception of the compon
5353
> the needs of dotty. Original authors include: Adrian Moors, Lukas Rytz,
5454
> Grzegorz Kossakowski, Paul Phillips
5555
56-
`dotty.tools.dotc.sbt`
56+
`dotty.tools.dotc.sbt and everything in bridge/`
5757

5858
> The sbt compiler phases are based on
5959
> https://github.com/adriaanm/scala/tree/sbt-api-consolidate/src/compiler/scala/tools/sbt
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
package xsbt
2+
3+
import java.net.{URL, URLClassLoader}
4+
5+
/** A classloader to run the compiler
6+
*
7+
* A CompilerClassLoader is constructed from a list of `urls` that need to be on
8+
* the classpath to run the compiler and the classloader used by sbt.
9+
*
10+
* To understand why a custom classloader is needed for the compiler, let us
11+
* describe some alternatives that wouldn't work.
12+
* - `new URLClassLoader(urls)`:
13+
* The compiler contains sbt phases that callback to sbt using the `xsbti.*`
14+
* interfaces. If `urls` does not contain the sbt interfaces we'll get a
15+
* `ClassNotFoundException` in the compiler when we try to use them, if
16+
* `urls` does contain the interfaces we'll get a `ClassCastException` or a
17+
* `LinkageError` because if the same class is loaded by two different
18+
* classloaders, they are considered distinct by the JVM.
19+
* - `new URLClassLoader(urls, sbtLoader)`:
20+
* Because of the JVM delegation model, this means that we will only load
21+
* a class from `urls` if it's not present in the parent `sbtLoader`, but
22+
* sbt uses its own version of the scala compiler and scala library which
23+
* is not the one we need to run the compiler.
24+
*
25+
* Our solution is to implement a subclass of URLClassLoader with no parent, instead
26+
* we override `loadClass` to load the `xsbti.*` interfaces from `sbtLoader`.
27+
*/
28+
class CompilerClassLoader(urls: Array[URL], sbtLoader: ClassLoader)
29+
extends URLClassLoader(urls, null) {
30+
override def loadClass(className: String, resolve: Boolean): Class[_] =
31+
if (className.startsWith("xsbti.")) {
32+
// We can't use the loadClass overload with two arguments because it's
33+
// protected, but we can do the same by hand (the classloader instance
34+
// from which we call resolveClass does not matter).
35+
val c = sbtLoader.loadClass(className)
36+
if (resolve)
37+
resolveClass(c)
38+
c
39+
} else {
40+
super.loadClass(className, resolve)
41+
}
42+
}
43+
44+
object CompilerClassLoader {
45+
/** Fix the compiler bridge ClassLoader
46+
*
47+
* Soundtrack: https://www.youtube.com/watch?v=imamcajBEJs
48+
*
49+
* The classloader that we get from sbt looks like:
50+
*
51+
* URLClassLoader(bridgeURLs,
52+
* DualLoader(scalaLoader, notXsbtiFilter, sbtLoader, xsbtiFilter))
53+
*
54+
* DualLoader will load the `xsbti.*` interfaces using `sbtLoader` and
55+
* everything else with `scalaLoader`. Once we have loaded the dotty Main
56+
* class using `scalaLoader`, subsequent classes in the dotty compiler will
57+
* also be loaded by `scalaLoader` and _not_ by the DualLoader. But the sbt
58+
* compiler phases are part of dotty and still need access to the `xsbti.*`
59+
* interfaces in `sbtLoader`, therefore DualLoader does not work for us
60+
* (this issue is not present with scalac because the sbt phases are
61+
* currently defined in the compiler bridge itself, not in scalac).
62+
*
63+
* CompilerClassLoader is a replacement for DualLoader. Until we can fix
64+
* this in sbt proper, we need to use reflection to construct our own
65+
* fixed classloader:
66+
*
67+
* URLClassLoader(bridgeURLs,
68+
* CompilerClassLoader(scalaLoader.getURLs, sbtLoader))
69+
*
70+
* @param bridgeLoader The classloader that sbt uses to load the compiler bridge
71+
* @return A fixed classloader that works with dotty
72+
*/
73+
def fixBridgeLoader(bridgeLoader: ClassLoader) = bridgeLoader match {
74+
case bridgeLoader: URLClassLoader =>
75+
val dualLoader = bridgeLoader.getParent
76+
val dualLoaderClass = dualLoader.getClass
77+
78+
// DualLoader#parentA and DualLoader#parentB are private
79+
val parentAField = dualLoaderClass.getDeclaredField("parentA")
80+
parentAField.setAccessible(true)
81+
val parentBField = dualLoaderClass.getDeclaredField("parentB")
82+
parentBField.setAccessible(true)
83+
val scalaLoader = parentAField.get(dualLoader).asInstanceOf[URLClassLoader]
84+
val sbtLoader = parentBField.get(dualLoader).asInstanceOf[URLClassLoader]
85+
86+
val bridgeURLs = bridgeLoader.getURLs
87+
new URLClassLoader(bridgeURLs,
88+
new CompilerClassLoader(scalaLoader.getURLs, sbtLoader))
89+
}
90+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/* sbt -- Simple Build Tool
2+
* Copyright 2008, 2009 Mark Harrah
3+
*/
4+
package xsbt
5+
6+
import xsbti.{ AnalysisCallback, Logger, Problem, Reporter, Severity, DependencyContext }
7+
import xsbti.api.SourceAPI
8+
import xsbti.compile._
9+
import Log.debug
10+
import java.io.File
11+
12+
import dotty.tools.dotc.core.Contexts.ContextBase
13+
import dotty.tools.dotc.{ Main => DottyMain }
14+
import dotty.tools.dotc.interfaces._
15+
16+
import java.net.URLClassLoader
17+
18+
final class CompilerInterface {
19+
def newCompiler(options: Array[String], output: Output, initialLog: Logger,
20+
initialDelegate: Reporter, resident: Boolean): CachedCompiler = {
21+
// The classloader that sbt uses to load the compiler bridge is broken
22+
// (see CompilerClassLoader#fixBridgeLoader for details). To workaround
23+
// this we construct our own ClassLoader and then run the following code
24+
// with it:
25+
// new CachedCompilerImpl(options, output, resident)
26+
27+
val bridgeLoader = getClass.getClassLoader
28+
val fixedLoader = CompilerClassLoader.fixBridgeLoader(bridgeLoader)
29+
val cciClass = fixedLoader.loadClass("xsbt.CachedCompilerImpl")
30+
cciClass.getConstructors.head
31+
.newInstance(options, output, resident: java.lang.Boolean)
32+
.asInstanceOf[CachedCompiler]
33+
}
34+
35+
def run(sources: Array[File], changes: DependencyChanges, callback: AnalysisCallback, log: Logger,
36+
delegate: Reporter, progress: CompileProgress, cached: CachedCompiler): Unit =
37+
cached.run(sources, changes, callback, log, delegate, progress)
38+
}
39+
40+
class CachedCompilerImpl(args: Array[String], output: Output, resident: Boolean) extends CachedCompiler {
41+
val outputArgs =
42+
output match {
43+
case multi: MultipleOutput =>
44+
???
45+
case single: SingleOutput =>
46+
List("-d", single.outputDirectory.getAbsolutePath.toString)
47+
}
48+
49+
def commandArguments(sources: Array[File]): Array[String] =
50+
(outputArgs ++ args.toList ++ sources.map(_.getAbsolutePath).sortWith(_ < _)).toArray[String]
51+
52+
def run(sources: Array[File], changes: DependencyChanges, callback: AnalysisCallback, log: Logger, delegate: Reporter, progress: CompileProgress): Unit = synchronized {
53+
run(sources.toList, changes, callback, log, progress)
54+
}
55+
private[this] def run(sources: List[File], changes: DependencyChanges, callback: AnalysisCallback, log: Logger, compileProgress: CompileProgress): Unit = {
56+
debug(log, args.mkString("Calling Dotty compiler with arguments (CompilerInterface):\n\t", "\n\t", ""))
57+
val ctx = (new ContextBase).initialCtx.fresh
58+
.setSbtCallback(callback)
59+
val cl = getClass.getClassLoader.asInstanceOf[URLClassLoader]
60+
61+
val reporter = DottyMain.process(commandArguments(sources.toArray), ctx)
62+
if (reporter.hasErrors) {
63+
throw new InterfaceCompileFailed(args, Array())
64+
}
65+
}
66+
}
67+
68+
class InterfaceCompileFailed(override val arguments: Array[String], override val problems: Array[Problem]) extends xsbti.CompileFailed {
69+
override val toString = "Compilation failed"
70+
}

bridge/src/main/scala/xsbt/Log.scala

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
/* sbt -- Simple Build Tool
2+
* Copyright 2008, 2009 Mark Harrah
3+
*/
4+
package xsbt
5+
6+
object Log {
7+
def debug(log: xsbti.Logger, msg: => String) = log.debug(Message(msg))
8+
def settingsError(log: xsbti.Logger): String => Unit =
9+
s => log.error(Message(s))
10+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/* sbt -- Simple Build Tool
2+
* Copyright 2008, 2009 Mark Harrah
3+
*/
4+
package xsbt
5+
6+
object Message {
7+
def apply[T](s: => T) = new xsbti.F0[T] { def apply() = s }
8+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
trait A {
2+
def x: Int
3+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
trait B extends A {
2+
override def x = 2
3+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
trait C extends A {
2+
def x = 5
3+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
trait D extends C with B
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
trait C extends A {
2+
abstract override def x = super.x + 5
3+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import sbt._
2+
import Keys._
3+
4+
object DottyInjectedPlugin extends AutoPlugin {
5+
override def requires = plugins.JvmPlugin
6+
override def trigger = allRequirements
7+
8+
override val projectSettings = Seq(
9+
scalaVersion := "0.1-SNAPSHOT",
10+
scalaOrganization := "ch.epfl.lamp",
11+
scalacOptions += "-language:Scala2",
12+
scalaBinaryVersion := "2.11",
13+
autoScalaLibrary := false,
14+
libraryDependencies ++= Seq("org.scala-lang" % "scala-library" % "2.11.5"),
15+
scalaCompilerBridgeSource := ("ch.epfl.lamp" % "dotty-bridge" % "0.1.1-SNAPSHOT" % "component").sources()
16+
)
17+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
> compile
2+
$ copy-file changes/C2.scala C.scala
3+
-> compile
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
InputKey[Unit]("check-number-of-compiler-iterations") <<= inputTask { (argTask: TaskKey[Seq[String]]) =>
2+
(argTask, compile in Compile) map { (args: Seq[String], a: sbt.inc.Analysis) =>
3+
assert(args.size == 1)
4+
val expectedIterationsNumber = args(0).toInt
5+
assert(a.compilations.allCompilations.size == expectedIterationsNumber, "a.compilations.allCompilations.size = %d (expected %d)".format(a.compilations.allCompilations.size, expectedIterationsNumber))
6+
}
7+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
object Bar {
2+
def bar: Outer.TypeInner = null
3+
// comment to trigger recompilation
4+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import sbt._
2+
import Keys._
3+
4+
object DottyInjectedPlugin extends AutoPlugin {
5+
override def requires = plugins.JvmPlugin
6+
override def trigger = allRequirements
7+
8+
override val projectSettings = Seq(
9+
scalaVersion := "0.1-SNAPSHOT",
10+
scalaOrganization := "ch.epfl.lamp",
11+
scalacOptions += "-language:Scala2",
12+
scalaBinaryVersion := "2.11",
13+
autoScalaLibrary := false,
14+
libraryDependencies ++= Seq("org.scala-lang" % "scala-library" % "2.11.5"),
15+
scalaCompilerBridgeSource := ("ch.epfl.lamp" % "dotty-bridge" % "0.1.1-SNAPSHOT" % "component").sources()
16+
)
17+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
object Bar {
2+
def bar: Outer.TypeInner = null
3+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
object Outer {
2+
class Inner { type Xyz }
3+
4+
type TypeInner = Inner { type Xyz = Int }
5+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
class Impl {
2+
def bleep = Bar.bar
3+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# Test for separate compilation and proper value of
2+
# the OVERRIDE flag when abstract types, type alias
3+
# and structural type are involved
4+
# See https://github.com/sbt/sbt/issues/726 for details
5+
6+
# introduces first compile iteration
7+
> compile
8+
# this change adds a comment and does not change api so introduces
9+
# only one additional compile iteration
10+
$ copy-file changes/Bar1.scala src/main/scala/Bar.scala
11+
# second iteration
12+
#> compile
13+
# check if there are only two compile iterations performed
14+
> check-number-of-compiler-iterations 2
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
trait A {
2+
type S[_]
3+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
trait B extends A {
2+
type F = S[Int]
3+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
trait A {
2+
type S
3+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import sbt._
2+
import Keys._
3+
4+
object DottyInjectedPlugin extends AutoPlugin {
5+
override def requires = plugins.JvmPlugin
6+
override def trigger = allRequirements
7+
8+
override val projectSettings = Seq(
9+
scalaVersion := "0.1-SNAPSHOT",
10+
scalaOrganization := "ch.epfl.lamp",
11+
scalacOptions += "-language:Scala2",
12+
scalaBinaryVersion := "2.11",
13+
autoScalaLibrary := false,
14+
libraryDependencies ++= Seq("org.scala-lang" % "scala-library" % "2.11.5"),
15+
scalaCompilerBridgeSource := ("ch.epfl.lamp" % "dotty-bridge" % "0.1.1-SNAPSHOT" % "component").sources()
16+
)
17+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
> compile
2+
3+
# remove type arguments from S
4+
$ copy-file changes/A.scala A.scala
5+
6+
# Both A.scala and B.scala should be recompiled, producing a compile error
7+
-> compile
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package example
2+
3+
object A
4+
{
5+
val x: Int = 3
6+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package example
2+
3+
object A
4+
{
5+
val x: Int = B.y
6+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package example
2+
3+
object A
4+
{
5+
val x: String = B.y
6+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package example
2+
3+
object B
4+
{
5+
val y: String = "4"
6+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package example
2+
3+
object B
4+
{
5+
val y: Int = 5
6+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import sbt._
2+
import Keys._
3+
4+
object DottyInjectedPlugin extends AutoPlugin {
5+
override def requires = plugins.JvmPlugin
6+
override def trigger = allRequirements
7+
8+
override val projectSettings = Seq(
9+
scalaVersion := "0.1-SNAPSHOT",
10+
scalaOrganization := "ch.epfl.lamp",
11+
scalacOptions += "-language:Scala2",
12+
scalaBinaryVersion := "2.11",
13+
autoScalaLibrary := false,
14+
libraryDependencies ++= Seq("org.scala-lang" % "scala-library" % "2.11.5"),
15+
scalaCompilerBridgeSource := ("ch.epfl.lamp" % "dotty-bridge" % "0.1.1-SNAPSHOT" % "component").sources()
16+
)
17+
}

0 commit comments

Comments
 (0)