From 10265efd9c0769016f7f29f0abfe3da78b2d6446 Mon Sep 17 00:00:00 2001 From: Guillaume Martres Date: Wed, 30 Dec 2015 22:38:55 +0100 Subject: [PATCH] Make sbt aware of Dotty This small set of changes, together with the compiler-bridge I wrote (https://github.com/smarter/dotty-bridge) enables us to compile code using Dotty in sbt, see https://github.com/smarter/dotty-example-project for an example. --- .../sbt/compiler/CompilerArguments.scala | 6 ++++- .../main/scala/sbt/compiler/RawCompiler.scala | 25 ++++++++++++++----- ivy/src/main/scala/sbt/IvyScala.scala | 16 +++++++++--- main/src/main/scala/sbt/Defaults.scala | 13 +++++++--- notes/0.13.10/dotty-awareness.md | 16 ++++++++++++ .../src/main/scala/sbt/ScalaInstance.scala | 5 ++++ 6 files changed, 67 insertions(+), 14 deletions(-) create mode 100644 notes/0.13.10/dotty-awareness.md diff --git a/compile/src/main/scala/sbt/compiler/CompilerArguments.scala b/compile/src/main/scala/sbt/compiler/CompilerArguments.scala index e383fdb56..295bae5bb 100644 --- a/compile/src/main/scala/sbt/compiler/CompilerArguments.scala +++ b/compile/src/main/scala/sbt/compiler/CompilerArguments.scala @@ -30,7 +30,11 @@ final class CompilerArguments(scalaInstance: xsbti.compile.ScalaInstance, cp: xs } def finishClasspath(classpath: Seq[File]): Seq[File] = filterLibrary(classpath) ++ include(cp.compiler, scalaInstance.compilerJar) ++ include(cp.extra, scalaInstance.otherJars: _*) - private[this] def include(flag: Boolean, jars: File*) = if (flag) jars else Nil + private[this] def include(flag: Boolean, jars: File*) = + if (flag || ScalaInstance.isDotty(scalaInstance.version)) + jars + else + Nil private[this] def abs(files: Seq[File]) = files.map(_.getAbsolutePath).sortWith(_ < _) private[this] def checkScalaHomeUnset(): Unit = { val scalaHome = System.getProperty("scala.home") diff --git a/compile/src/main/scala/sbt/compiler/RawCompiler.scala b/compile/src/main/scala/sbt/compiler/RawCompiler.scala index 74f89a7e1..a739c64c8 100644 --- a/compile/src/main/scala/sbt/compiler/RawCompiler.scala +++ b/compile/src/main/scala/sbt/compiler/RawCompiler.scala @@ -21,16 +21,29 @@ class RawCompiler(val scalaInstance: xsbti.compile.ScalaInstance, cp: ClasspathO val arguments = compilerArguments(sources, classpath, Some(outputDirectory), options) log.debug("Plain interface to Scala compiler " + scalaInstance.actualVersion + " with arguments: " + arguments.mkString("\n\t", "\n\t", "")) - val mainClass = Class.forName("scala.tools.nsc.Main", true, scalaInstance.loader) - val process = mainClass.getMethod("process", classOf[Array[String]]) - process.invoke(null, arguments.toArray) - checkForFailure(mainClass, arguments.toArray) + val args = arguments.toArray + val reporter = + if (ScalaInstance.isDotty(scalaInstance.version)) { + val mainClass = Class.forName("dotty.tools.dotc.Main", true, scalaInstance.loader) + val process = mainClass.getMethod("process", classOf[Array[String]]) + process.invoke(null, args) + } else { + val mainClass = Class.forName("scala.tools.nsc.Main", true, scalaInstance.loader) + val process = mainClass.getMethod("process", classOf[Array[String]]) + process.invoke(null, arguments.toArray) + mainClass.getMethod("reporter").invoke(null) + } + checkForFailure(reporter, arguments.toArray) } def compilerArguments = new CompilerArguments(scalaInstance, cp) - protected def checkForFailure(mainClass: Class[_], args: Array[String]): Unit = { - val reporter = mainClass.getMethod("reporter").invoke(null) + protected def checkForFailure(reporter: AnyRef, args: Array[String]): Unit = { val failed = reporter.getClass.getMethod("hasErrors").invoke(reporter).asInstanceOf[Boolean] if (failed) throw new CompileFailed(args, "Plain compile failed", Array()) } + @deprecated("Use `checkForFailure(AnyRef, Array[String])`", "0.13.10") + protected def checkForFailure(mainClass: Class[_], args: Array[String]): Unit = { + val reporter = mainClass.getMethod("reporter").invoke(null) + checkForFailure(reporter, args) + } } class CompileFailed(val arguments: Array[String], override val toString: String, val problems: Array[xsbti.Problem]) extends xsbti.CompileFailed with FeedbackProvidedException diff --git a/ivy/src/main/scala/sbt/IvyScala.scala b/ivy/src/main/scala/sbt/IvyScala.scala index 2614df994..81d19dd87 100644 --- a/ivy/src/main/scala/sbt/IvyScala.scala +++ b/ivy/src/main/scala/sbt/IvyScala.scala @@ -17,12 +17,20 @@ object ScalaArtifacts { val LibraryID = ScalaLibraryID val CompilerID = ScalaCompilerID val ReflectID = "scala-reflect" + val DottyIDPrefix = "dotty" + + def dottyID(binaryVersion: String): String = s"${DottyIDPrefix}_${binaryVersion}" + def libraryDependency(version: String): ModuleID = ModuleID(Organization, LibraryID, version) - private[sbt] def toolDependencies(org: String, version: String): Seq[ModuleID] = Seq( - scalaToolDependency(org, ScalaArtifacts.CompilerID, version), - scalaToolDependency(org, ScalaArtifacts.LibraryID, version) - ) + private[sbt] def toolDependencies(org: String, version: String, isDotty: Boolean = false): Seq[ModuleID] = + if (isDotty) + Seq(ModuleID(org, DottyIDPrefix, version, Some(Configurations.ScalaTool.name + "->compile"), + crossVersion = CrossVersion.binary)) + else + Seq(scalaToolDependency(org, ScalaArtifacts.CompilerID, version), + scalaToolDependency(org, ScalaArtifacts.LibraryID, version)) + private[this] def scalaToolDependency(org: String, id: String, version: String): ModuleID = ModuleID(org, id, version, Some(Configurations.ScalaTool.name + "->default,optional(default)")) } diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index 9c68b0e23..067f120f1 100755 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -390,7 +390,11 @@ object Defaults extends BuildCommon { def file(id: String) = files(id).headOption getOrElse sys.error(s"Missing ${id}.jar") val allFiles = toolReport.modules.flatMap(_.artifacts.map(_._2)) val libraryJar = file(ScalaArtifacts.LibraryID) - val compilerJar = file(ScalaArtifacts.CompilerID) + val compilerJar = + if (ScalaInstance.isDotty(scalaVersion.value)) + file(ScalaArtifacts.dottyID(scalaBinaryVersion.value)) + else + file(ScalaArtifacts.CompilerID) val otherJars = allFiles.filterNot(x => x == libraryJar || x == compilerJar) ScalaInstance(scalaVersion.value, libraryJar, compilerJar, otherJars: _*)(makeClassLoader(state.value)) } @@ -1250,8 +1254,11 @@ object Classpaths { val pluginAdjust = if (sbtPlugin.value) sbtDependency.value.copy(configurations = Some(Provided.name)) +: base else base if (scalaHome.value.isDefined || ivyScala.value.isEmpty || !managedScalaInstance.value) pluginAdjust - else - ScalaArtifacts.toolDependencies(scalaOrganization.value, scalaVersion.value) ++ pluginAdjust + else { + val version = scalaVersion.value + val isDotty = ScalaInstance.isDotty(version) + ScalaArtifacts.toolDependencies(scalaOrganization.value, version, isDotty) ++ pluginAdjust + } } ) @deprecated("Split into ivyBaseSettings and jvmBaseSettings.", "0.13.2") diff --git a/notes/0.13.10/dotty-awareness.md b/notes/0.13.10/dotty-awareness.md new file mode 100644 index 000000000..5660f9bcd --- /dev/null +++ b/notes/0.13.10/dotty-awareness.md @@ -0,0 +1,16 @@ + + [Dotty]: https://github.com/lampepfl/dotty + [@smarter]: https://github.com/smarter + +### Fixes with compatibility implications + +### Improvements + +- sbt is now aware of [Dotty][Dotty], it will assume + that Dotty is used when `scalaVersion` starts with `0.`, the sbt + compiler-bridge does not support Dotty but a separate compiler-bridge is being + developed at https://github.com/smarter/dotty-bridge and an example project + that uses it is available at https://github.com/smarter/dotty-example-project + by [@smarter][@smarter]. + +### Bug fixes diff --git a/util/classpath/src/main/scala/sbt/ScalaInstance.scala b/util/classpath/src/main/scala/sbt/ScalaInstance.scala index b2bf5bc3d..bd91b3da7 100644 --- a/util/classpath/src/main/scala/sbt/ScalaInstance.scala +++ b/util/classpath/src/main/scala/sbt/ScalaInstance.scala @@ -41,6 +41,11 @@ object ScalaInstance { val ScalaOrg = ScalaOrganization val VersionPrefix = "version " + def isDotty(version: String): Boolean = + // We rely on the fact that the first public version of Scala was 1.0 and + // that Dotty will keep being version 0.x for a long time. + version.startsWith("0.") + def apply(org: String, version: String, launcher: xsbti.Launcher): ScalaInstance = // Due to incompatibility with previous launchers if scalaOrg has default value revert to an existing method if (org == ScalaOrg)