Skip to content

Merge the sbt compiler bridge as a subproject of dotty #1306

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 7, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
2 changes: 1 addition & 1 deletion AUTHORS.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ The majority of the dotty codebase is new code, with the exception of the compon
> the needs of dotty. Original authors include: Adrian Moors, Lukas Rytz,
> Grzegorz Kossakowski, Paul Phillips

`dotty.tools.dotc.sbt`
`dotty.tools.dotc.sbt and everything in bridge/`

> The sbt compiler phases are based on
> https://github.com/adriaanm/scala/tree/sbt-api-consolidate/src/compiler/scala/tools/sbt
Expand Down
90 changes: 90 additions & 0 deletions bridge/src/main/scala/xsbt/CompilerClassLoader.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package xsbt

import java.net.{URL, URLClassLoader}

/** 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`.
*/
class CompilerClassLoader(urls: Array[URL], sbtLoader: ClassLoader)
extends URLClassLoader(urls, null) {
override def loadClass(className: String, resolve: Boolean): Class[_] =
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).
val c = sbtLoader.loadClass(className)
if (resolve)
resolveClass(c)
c
} else {
super.loadClass(className, resolve)
}
}

object CompilerClassLoader {
/** 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
*/
def fixBridgeLoader(bridgeLoader: ClassLoader) = bridgeLoader match {
case bridgeLoader: URLClassLoader =>
val dualLoader = bridgeLoader.getParent
val dualLoaderClass = dualLoader.getClass

// DualLoader#parentA and DualLoader#parentB are private
val parentAField = dualLoaderClass.getDeclaredField("parentA")
parentAField.setAccessible(true)
val parentBField = dualLoaderClass.getDeclaredField("parentB")
parentBField.setAccessible(true)
val scalaLoader = parentAField.get(dualLoader).asInstanceOf[URLClassLoader]
val sbtLoader = parentBField.get(dualLoader).asInstanceOf[URLClassLoader]

val bridgeURLs = bridgeLoader.getURLs
new URLClassLoader(bridgeURLs,
new CompilerClassLoader(scalaLoader.getURLs, sbtLoader))
}
}
70 changes: 70 additions & 0 deletions bridge/src/main/scala/xsbt/CompilerInterface.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/* sbt -- Simple Build Tool
* Copyright 2008, 2009 Mark Harrah
*/
package xsbt

import xsbti.{ AnalysisCallback, Logger, Problem, Reporter, Severity, DependencyContext }
import xsbti.api.SourceAPI
import xsbti.compile._
import Log.debug
import java.io.File

import dotty.tools.dotc.core.Contexts.ContextBase
import dotty.tools.dotc.{ Main => DottyMain }
import dotty.tools.dotc.interfaces._

import java.net.URLClassLoader

final class CompilerInterface {
def newCompiler(options: Array[String], output: Output, initialLog: Logger,
initialDelegate: Reporter, resident: Boolean): CachedCompiler = {
// The classloader that sbt uses to load the compiler bridge is broken
// (see CompilerClassLoader#fixBridgeLoader for details). To workaround
// this we construct our own ClassLoader and then run the following code
// with it:
// new CachedCompilerImpl(options, output, resident)

val bridgeLoader = getClass.getClassLoader
val fixedLoader = CompilerClassLoader.fixBridgeLoader(bridgeLoader)
val cciClass = fixedLoader.loadClass("xsbt.CachedCompilerImpl")
cciClass.getConstructors.head
.newInstance(options, output, resident: java.lang.Boolean)
.asInstanceOf[CachedCompiler]
}

def run(sources: Array[File], changes: DependencyChanges, callback: AnalysisCallback, log: Logger,
delegate: Reporter, progress: CompileProgress, cached: CachedCompiler): Unit =
cached.run(sources, changes, callback, log, delegate, progress)
}

class CachedCompilerImpl(args: Array[String], output: Output, resident: Boolean) extends CachedCompiler {
val outputArgs =
output match {
case multi: MultipleOutput =>
???
case single: SingleOutput =>
List("-d", single.outputDirectory.getAbsolutePath.toString)
}

def commandArguments(sources: Array[File]): Array[String] =
(outputArgs ++ args.toList ++ sources.map(_.getAbsolutePath).sortWith(_ < _)).toArray[String]

def run(sources: Array[File], changes: DependencyChanges, callback: AnalysisCallback, log: Logger, delegate: Reporter, progress: CompileProgress): Unit = synchronized {
run(sources.toList, changes, callback, log, progress)
}
private[this] def run(sources: List[File], changes: DependencyChanges, callback: AnalysisCallback, log: Logger, compileProgress: CompileProgress): Unit = {
debug(log, args.mkString("Calling Dotty compiler with arguments (CompilerInterface):\n\t", "\n\t", ""))
val ctx = (new ContextBase).initialCtx.fresh
.setSbtCallback(callback)
val cl = getClass.getClassLoader.asInstanceOf[URLClassLoader]

val reporter = DottyMain.process(commandArguments(sources.toArray), ctx)
if (reporter.hasErrors) {
throw new InterfaceCompileFailed(args, Array())
}
}
}

class InterfaceCompileFailed(override val arguments: Array[String], override val problems: Array[Problem]) extends xsbti.CompileFailed {
override val toString = "Compilation failed"
}
10 changes: 10 additions & 0 deletions bridge/src/main/scala/xsbt/Log.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/* sbt -- Simple Build Tool
* Copyright 2008, 2009 Mark Harrah
*/
package xsbt

object Log {
def debug(log: xsbti.Logger, msg: => String) = log.debug(Message(msg))
def settingsError(log: xsbti.Logger): String => Unit =
s => log.error(Message(s))
}
8 changes: 8 additions & 0 deletions bridge/src/main/scala/xsbt/Message.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/* sbt -- Simple Build Tool
* Copyright 2008, 2009 Mark Harrah
*/
package xsbt

object Message {
def apply[T](s: => T) = new xsbti.F0[T] { def apply() = s }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
trait A {
def x: Int
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
trait B extends A {
override def x = 2
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
trait C extends A {
def x = 5
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
trait D extends C with B
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
trait C extends A {
abstract override def x = super.x + 5
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import sbt._
import Keys._

object DottyInjectedPlugin extends AutoPlugin {
override def requires = plugins.JvmPlugin
override def trigger = allRequirements

override val projectSettings = Seq(
scalaVersion := "0.1-SNAPSHOT",
scalaOrganization := "ch.epfl.lamp",
scalacOptions += "-language:Scala2",
scalaBinaryVersion := "2.11",
autoScalaLibrary := false,
libraryDependencies ++= Seq("org.scala-lang" % "scala-library" % "2.11.5"),
scalaCompilerBridgeSource := ("ch.epfl.lamp" % "dotty-bridge" % "0.1.1-SNAPSHOT" % "component").sources()
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
> compile
$ copy-file changes/C2.scala C.scala
-> compile
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
InputKey[Unit]("check-number-of-compiler-iterations") <<= inputTask { (argTask: TaskKey[Seq[String]]) =>
(argTask, compile in Compile) map { (args: Seq[String], a: sbt.inc.Analysis) =>
assert(args.size == 1)
val expectedIterationsNumber = args(0).toInt
assert(a.compilations.allCompilations.size == expectedIterationsNumber, "a.compilations.allCompilations.size = %d (expected %d)".format(a.compilations.allCompilations.size, expectedIterationsNumber))
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
object Bar {
def bar: Outer.TypeInner = null
// comment to trigger recompilation
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import sbt._
import Keys._

object DottyInjectedPlugin extends AutoPlugin {
override def requires = plugins.JvmPlugin
override def trigger = allRequirements

override val projectSettings = Seq(
scalaVersion := "0.1-SNAPSHOT",
scalaOrganization := "ch.epfl.lamp",
scalacOptions += "-language:Scala2",
scalaBinaryVersion := "2.11",
autoScalaLibrary := false,
libraryDependencies ++= Seq("org.scala-lang" % "scala-library" % "2.11.5"),
scalaCompilerBridgeSource := ("ch.epfl.lamp" % "dotty-bridge" % "0.1.1-SNAPSHOT" % "component").sources()
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
object Bar {
def bar: Outer.TypeInner = null
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
object Outer {
class Inner { type Xyz }

type TypeInner = Inner { type Xyz = Int }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
class Impl {
def bleep = Bar.bar
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Test for separate compilation and proper value of
# the OVERRIDE flag when abstract types, type alias
# and structural type are involved
# See https://github.com/sbt/sbt/issues/726 for details

# introduces first compile iteration
> compile
# this change adds a comment and does not change api so introduces
# only one additional compile iteration
$ copy-file changes/Bar1.scala src/main/scala/Bar.scala
# second iteration
#> compile
# check if there are only two compile iterations performed
> check-number-of-compiler-iterations 2
3 changes: 3 additions & 0 deletions bridge/src/sbt-test/source-dependencies/abstract-type/A.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
trait A {
type S[_]
}
3 changes: 3 additions & 0 deletions bridge/src/sbt-test/source-dependencies/abstract-type/B.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
trait B extends A {
type F = S[Int]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
trait A {
type S
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import sbt._
import Keys._

object DottyInjectedPlugin extends AutoPlugin {
override def requires = plugins.JvmPlugin
override def trigger = allRequirements

override val projectSettings = Seq(
scalaVersion := "0.1-SNAPSHOT",
scalaOrganization := "ch.epfl.lamp",
scalacOptions += "-language:Scala2",
scalaBinaryVersion := "2.11",
autoScalaLibrary := false,
libraryDependencies ++= Seq("org.scala-lang" % "scala-library" % "2.11.5"),
scalaCompilerBridgeSource := ("ch.epfl.lamp" % "dotty-bridge" % "0.1.1-SNAPSHOT" % "component").sources()
)
}
7 changes: 7 additions & 0 deletions bridge/src/sbt-test/source-dependencies/abstract-type/test
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
> compile

# remove type arguments from S
$ copy-file changes/A.scala A.scala

# Both A.scala and B.scala should be recompiled, producing a compile error
-> compile
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package example

object A
{
val x: Int = 3
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package example

object A
{
val x: Int = B.y
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package example

object A
{
val x: String = B.y
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package example

object B
{
val y: String = "4"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package example

object B
{
val y: Int = 5
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import sbt._
import Keys._

object DottyInjectedPlugin extends AutoPlugin {
override def requires = plugins.JvmPlugin
override def trigger = allRequirements

override val projectSettings = Seq(
scalaVersion := "0.1-SNAPSHOT",
scalaOrganization := "ch.epfl.lamp",
scalacOptions += "-language:Scala2",
scalaBinaryVersion := "2.11",
autoScalaLibrary := false,
libraryDependencies ++= Seq("org.scala-lang" % "scala-library" % "2.11.5"),
scalaCompilerBridgeSource := ("ch.epfl.lamp" % "dotty-bridge" % "0.1.1-SNAPSHOT" % "component").sources()
)
}
Loading