diff --git a/backend/src/main/scala/bloop/Compiler.scala b/backend/src/main/scala/bloop/Compiler.scala index b7750b3adb..dc5e5a982f 100644 --- a/backend/src/main/scala/bloop/Compiler.scala +++ b/backend/src/main/scala/bloop/Compiler.scala @@ -5,6 +5,7 @@ import xsbti.T2 import java.util.Optional import java.io.File +import bloop.internal.Ecosystem import bloop.io.{AbsolutePath, Paths} import bloop.logging.Logger import bloop.reporter.Reporter @@ -75,7 +76,10 @@ object Compiler { val reporter = compileInputs.reporter val compilerCache = new FreshCompilerCache val cacheFile = compileInputs.baseDirectory.resolve("cache").toFile - val incOptions = IncOptions.create() + val incOptions = { + if (!compileInputs.scalaInstance.isDotty) IncOptions.create() + else Ecosystem.supportDotty(IncOptions.create()) + } val progress = Optional.empty[CompileProgress] Setup.create(lookup, skip, cacheFile, compilerCache, incOptions, reporter, progress, empty) } diff --git a/backend/src/main/scala/bloop/ScalaInstance.scala b/backend/src/main/scala/bloop/ScalaInstance.scala index cb099470b7..f4fbec21de 100644 --- a/backend/src/main/scala/bloop/ScalaInstance.scala +++ b/backend/src/main/scala/bloop/ScalaInstance.scala @@ -20,7 +20,7 @@ class ScalaInstance( private def isJar(filename: String): Boolean = filename.endsWith(".jar") private def hasScalaCompilerName(filename: String): Boolean = - filename.startsWith("scala-compiler") + if (isDotty) filename.startsWith("dotty-compiler") else filename.startsWith("scala-compiler") private def hasScalaLibraryName(filename: String): Boolean = filename.startsWith("scala-library") @@ -33,6 +33,10 @@ class ScalaInstance( isJar(filename) && !hasScalaCompilerName(filename) && !hasScalaLibraryName(filename) } + /** Is this `ScalaInstance` using Dotty? */ + def isDotty: Boolean = + organization == "ch.epfl.lamp" && sbt.internal.inc.ScalaInstance.isDotty(version) + /** Tells us what the real version of the classloaded scalac compiler in this instance is. */ override def actualVersion(): String = { // TODO: Report when the `actualVersion` and the passed in version do not match. diff --git a/backend/src/main/scala/bloop/internal/Ecosystem.scala b/backend/src/main/scala/bloop/internal/Ecosystem.scala new file mode 100644 index 0000000000..63f80d2385 --- /dev/null +++ b/backend/src/main/scala/bloop/internal/Ecosystem.scala @@ -0,0 +1,45 @@ +package bloop.internal + +import java.io.File +import java.util.Optional + +import xsbti.compile.{ + ClassFileManager, + ClassFileManagerUtil, + DefaultExternalHooks, + IncOptions, + WrappedClassFileManager +} + +object Ecosystem { + def supportDotty(incOptions: IncOptions): IncOptions = { + val tastyFileManager = new ClassFileManager { + private[this] val inherited = ClassFileManagerUtil.getDefaultClassFileManager(incOptions) + + def delete(classes: Array[File]): Unit = { + val tastySuffixes = List(".tasty", ".hasTasty") + inherited.delete(classes flatMap { classFile => + if (classFile.getPath endsWith ".class") { + val prefix = classFile.getAbsolutePath.stripSuffix(".class") + tastySuffixes.map(suffix => new File(prefix + suffix)).filter(_.exists) + } else Nil + }) + } + + def generated(classes: Array[File]): Unit = {} + def complete(success: Boolean): Unit = {} + } + val inheritedHooks = incOptions.externalHooks + val externalClassFileManager: Optional[ClassFileManager] = Option( + inheritedHooks.getExternalClassFileManager.orElse(null)) match { + case Some(prevManager) => + Optional.of(WrappedClassFileManager.of(prevManager, Optional.of(tastyFileManager))) + case None => + Optional.of(tastyFileManager) + } + + val newExternalHooks = + new DefaultExternalHooks(inheritedHooks.getExternalLookup, externalClassFileManager) + incOptions.withExternalHooks(newExternalHooks) + } +} diff --git a/backend/src/main/scala/sbt/internal/inc/bloop/ZincInternals.scala b/backend/src/main/scala/sbt/internal/inc/bloop/ZincInternals.scala index 16dd42d888..ea6a417d87 100644 --- a/backend/src/main/scala/sbt/internal/inc/bloop/ZincInternals.scala +++ b/backend/src/main/scala/sbt/internal/inc/bloop/ZincInternals.scala @@ -22,15 +22,19 @@ object ZincInternals { def compilerBridgeId(scalaVersion: String) = { // Defaults to bridge for 2.13 for Scala versions bigger than 2.13.x scalaVersion match { + case sc if (sc startsWith "0.") => "dotty-sbt-bridge" case sc if (sc startsWith "2.10.") => "compiler-bridge_2.10" case sc if (sc startsWith "2.11.") => "compiler-bridge_2.11" case sc if (sc startsWith "2.12.") => "compiler-bridge_2.12" - case _ => "compiler-bridge_2.13" + case _ => "compiler-bridge_2.13" } } + val organization = if (scalaInstance.isDotty) scalaInstance.organization else "ch.epfl.scala" val bridgeId = compilerBridgeId(scalaInstance.version) - ModuleID("ch.epfl.scala", bridgeId, latestVersion).withConfigurations(CompileConf).sources() + val version = if (scalaInstance.isDotty) scalaInstance.version else latestVersion + + ModuleID(organization, bridgeId, version).withConfigurations(CompileConf).sources() } /** diff --git a/frontend/src/test/scala/bloop/tasks/CompilationTaskTest.scala b/frontend/src/test/scala/bloop/tasks/CompilationTaskTest.scala index d82138f8b1..33f675187d 100644 --- a/frontend/src/test/scala/bloop/tasks/CompilationTaskTest.scala +++ b/frontend/src/test/scala/bloop/tasks/CompilationTaskTest.scala @@ -24,6 +24,7 @@ class CompilationTaskTest { val `B2.scala` = "package p1\ntrait B" val `C.scala` = "package p2\nimport p0.A\nimport p1.B\nobject C extends A with B" val `C2.scala` = "package p2\nimport p0.A\nobject C extends A" + val `Dotty.scala` = "package p0\nobject Foo { val x: String | Int = 1 }" } @Test @@ -232,4 +233,16 @@ class CompilationTaskTest { assert(msgs.exists(m => m._1 == "warn" && m._2.contains(targetMsg))) } } + + @Test + def compileWithDotty080RC1: Unit = { + val logger = new RecordingLogger() + val scalaInstance = + ScalaInstance.resolve("ch.epfl.lamp", "dotty-compiler_0.8", "0.8.0-RC1", logger) + val structures = Map(RootProject -> Map("Dotty.scala" -> ArtificialSources.`Dotty.scala`)) + checkAfterCleanCompilation(structures, Map.empty, scalaInstance = scalaInstance) { state => + val projects = state.build.projects + assert(projects.forall(p => hasPreviousResult(p, state))) + } + } }