Skip to content

Commit

Permalink
Minimal scala3-compiler-bridge
Browse files Browse the repository at this point in the history
The `scala3-compiler-bridge` lives in its own module because it shares
almost no sources with the Scala 2 `compiler-bridge`. It is compiled by
Scala 3, using `sbt-dotty`, because it depends on the Scala 3 compiler,
which is compiled in Scala 3. As soon as `sbt-dotty` is merged into
sbt, the `scala3-compiler-bridge` should be able to compiles itself.

This first minimal implementation is a direct translation of
`sbt-bridge` in `https://github.com/lampepfl/dotty`:
- https://github.com/lampepfl/dotty/blob/master/sbt-bridge/src/xsbt/CompilerInterface.java
- https://github.com/lampepfl/dotty/blob/master/sbt-bridge/src/xsbt/CachedCompilerImpl.java

One minor difference is that it implements the new
`xsbti.compile.CompilerInterface2`.
  • Loading branch information
adpi2 committed Nov 26, 2020
1 parent 757a0ea commit 91e77ea
Show file tree
Hide file tree
Showing 7 changed files with 272 additions and 1 deletion.
19 changes: 19 additions & 0 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -510,6 +510,25 @@ lazy val compilerBridge211 = compilerBridge.jvm(scala211)
lazy val compilerBridge212 = compilerBridge.jvm(scala212)
lazy val compilerBridge213 = compilerBridge.jvm(scala213)

lazy val scala3CompilerBridge = project
.in(internalPath / "scala3-compiler-bridge")
.dependsOn(
compilerInterface.jvm(autoScalaLibrary = false)
)
.settings(
name := "scala3-compiler-bridge",
autoScalaLibrary := false,
scalaVersion :=`scala-3.0`,
baseSettings,
compilerVersionDependentScalacOptions,
libraryDependencies += scalaCompiler.value % "provided",
exportJars := true,
// Those scalacOptions are ignored by the scala3-compiler
Compile / scalacOptions --=
Seq("-Xlint", "-Ywarn-dead-code", "-Ywarn-numeric-widen", "-Ywarn-value-discard")
)
.disablePlugins(ScalafmtPlugin)

/**
* Tests for the compiler bridge.
* This is split into a separate subproject because testing introduces more dependencies
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
xsbt.CompilerBridge
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
/*
* Zinc - The incremental compiler for Scala.
* Copyright Lightbend, Inc. and Mark Harrah
*
* Licensed under Apache License 2.0
* (http://www.apache.org/licenses/LICENSE-2.0).
*
* See the NOTICE file distributed with this work for
* additional information regarding copyright ownership.
*/

package xsbt

import dotty.tools.dotc.Main
import dotty.tools.dotc.config.Properties
import dotty.tools.dotc.core.Contexts.ContextBase
import xsbt.Log.debug
import xsbti.{ AnalysisCallback, Logger, PathBasedFile, Problem, Reporter, VirtualFile }
import xsbti.compile._

/**
* This is the entry point for the compiler bridge (implementation of CompilerInterface2)
*/
final class CompilerBridge extends xsbti.compile.CompilerInterface2 {
override def run(
sources: Array[VirtualFile],
changes: DependencyChanges,
options: Array[String],
output: Output,
callback: AnalysisCallback,
delegate: Reporter,
progress: CompileProgress,
log: Logger
): Unit = {
val cached = new CachedCompilerImpl(options, output)
cached.run(sources, callback, log, delegate)
}
}

private final class CachedCompilerImpl(
args: Array[String],
output: Output
) {

private val outputArgs = output match {
case output: SingleOutput =>
Array("-d", output.getOutputDirectoryAsPath.toString)
case _ =>
throw new IllegalArgumentException(
s"output should be a SingleOutput, was a ${output.getClass.getName}"
);
}

private def commandArguments(sources: Array[VirtualFile]): Array[String] = {
val files = sources.map {
case pathBased: PathBasedFile => pathBased
case virtualFile =>
throw new IllegalArgumentException(
s"source should be a PathBasedFile, was ${virtualFile.getClass.getName}"
)
}
outputArgs ++ args ++ files.map(_.toPath.toString)
}

private def infoOnCachedCompiler(compilerId: String): String =
s"[zinc] Running cached compiler $compilerId for Scala compiler ${Properties.versionString}"

private def prettyPrintCompilationArguments(args: Array[String]) =
args.mkString("[zinc] The Scala compiler is invoked with:\n\t", "\n\t", "")

def run(
sources: Array[VirtualFile],
callback: AnalysisCallback,
log: Logger,
delegate: Reporter,
): Unit = synchronized {
debug(log, infoOnCachedCompiler(hashCode().toLong.toHexString))
debug(log, prettyPrintCompilationArguments(args))
val dreporter = new DelegatingReporter(delegate)
try {
val ctx = new ContextBase().initialCtx.fresh
.setSbtCallback(callback)
.setReporter(dreporter)
val reporter = Main.process(commandArguments(sources), ctx)
if (reporter.hasErrors)
throw new InterfaceCompileFailed(args, Array(), "Compilation failed")
} finally {
dreporter.dropDelegate()
}
}
}

class InterfaceCompileFailed(
val arguments: Array[String],
val problems: Array[Problem],
override val toString: String
) extends xsbti.CompileFailed
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
/*
* Zinc - The incremental compiler for Scala.
* Copyright Lightbend, Inc. and Mark Harrah
*
* Licensed under Apache License 2.0
* (http://www.apache.org/licenses/LICENSE-2.0).
*
* See the NOTICE file distributed with this work for
* additional information regarding copyright ownership.
*/

package xsbt

import java.io.File
import java.util.Optional

import dotty.tools.dotc.core.Contexts.Context
import dotty.tools.dotc.reporting.{ AbstractReporter, Diagnostic }
import dotty.tools.dotc.interfaces
import dotty.tools.dotc.util.{ SourceFile, SourcePosition }
import xsbti.{ Position, Problem, Severity }

private final class DelegatingReporter(
private[this] var delegate: xsbti.Reporter
) extends AbstractReporter {
import DelegatingReporter._
def dropDelegate(): Unit = { delegate = null }

override def printSummary(using ctx: Context): Unit = delegate.printSummary()

override def doReport(diagnostic: Diagnostic)(using Context): Unit = {
val severity = convert(diagnostic.level)
val position = convert(diagnostic.pos)
val message = diagnostic.msg
val explain = if (Diagnostic.shouldExplain(diagnostic) && message.explanation.nonEmpty) {
explanation(message)
} else ""
val rendered = messageAndPos(message, diagnostic.pos, diagnosticLevel(diagnostic)) + explain
delegate.log(CompileProblem(position, message.message, severity, rendered))
}
}

private object DelegatingReporter {
private def convert(pos: SourcePosition): Position = {
if (pos.exists) new PositionImpl(pos, pos.source)
else EmptyPosition
}

private def convert(level: Int): xsbti.Severity = {
level match {
case interfaces.Diagnostic.ERROR => Severity.Error
case interfaces.Diagnostic.WARNING => Severity.Warn
case interfaces.Diagnostic.INFO => Severity.Info
case level => throw new IllegalArgumentException(s"Bad diagnostic level: $level")
}
}

object EmptyPosition extends xsbti.Position {
override def line(): Optional[Integer] = Optional.empty
override def lineContent(): String = ""
override def offset(): Optional[Integer] = Optional.empty
override def pointer(): Optional[Integer] = Optional.empty
override def pointerSpace(): Optional[String] = Optional.empty
override def sourcePath(): Optional[String] = Optional.empty
override def sourceFile(): Optional[File] = Optional.empty
}

class PositionImpl(
pos: SourcePosition,
src: SourceFile
) extends xsbti.Position {
def line: Optional[Integer] = {
if (src.content.isEmpty)
Optional.empty
else {
val line = pos.line
if (line == -1) Optional.empty
else Optional.of(line + 1)
}
}
def lineContent: String = {
if (src.content.isEmpty) ""
else {
val line = pos.lineContent
if (line.endsWith("\r\n"))
line.substring(0, line.length - 2)
else if (line.endsWith("\n") || line.endsWith("\u000c"))
line.substring(0, line.length - 1)
else line
}
}
def offset: Optional[Integer] = Optional.of(pos.point)
def sourcePath: Optional[String] = {
if (!src.exists) Optional.empty
else Optional.ofNullable(src.file.path)
}
def sourceFile: Optional[File] = {
if (!src.exists) Optional.empty
else Optional.ofNullable(src.file.file)
}
def pointer: Optional[Integer] = {
if (src.content.isEmpty) Optional.empty
else Optional.of(pos.point - src.startOfLine(pos.point))
}
def pointerSpace: Optional[String] = {
if (src.content.isEmpty) Optional.empty
else {
// Don't crash if pointer is out-of-bounds (happens with some macros)
val fixedPointer = Math.min(pointer.get, lineContent.length)
val result = lineContent
.take(fixedPointer)
.map {
case '\t' => '\t'
case _ => ' '
}
Optional.of(result)
}
}
}

private final case class CompileProblem(
position: Position,
message: String,
severity: Severity,
_rendered: String
) extends Problem {
override def category = ""
override def rendered: Optional[String] = Optional.of(_rendered)
}
}
16 changes: 16 additions & 0 deletions internal/scala3-compiler-bridge/src/main/scala/xsbt/Log.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/*
* Zinc - The incremental compiler for Scala.
* Copyright Lightbend, Inc. and Mark Harrah
*
* Licensed under Apache License 2.0
* (http://www.apache.org/licenses/LICENSE-2.0).
*
* See the NOTICE file distributed with this work for
* additional information regarding copyright ownership.
*/

package xsbt

object Log {
def debug(log: xsbti.Logger, msg: => String) = log.debug(() => msg)
}
9 changes: 8 additions & 1 deletion project/Dependencies.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ object Dependencies {
val scala211 = "2.11.12"
val scala212 = "2.12.12"
val scala213 = "2.13.3"
val `scala-3.0` = "3.0.0-M1"

val defaultScalaVersion = scala212
val allScalaVersions = Seq(defaultScalaVersion, scala210, scala211, scala213)
val scala212_213 = Seq(defaultScalaVersion, scala213)
Expand Down Expand Up @@ -68,7 +70,12 @@ object Dependencies {
addSbtModule(p, sbtUtilPath, "utilTracking", utilTracking)

val scalaLibrary = Def.setting { "org.scala-lang" % "scala-library" % scalaVersion.value }
val scalaCompiler = Def.setting { "org.scala-lang" % "scala-compiler" % scalaVersion.value }
val scalaCompiler = Def.setting {
if (scalaVersion.value.startsWith("3."))
"org.scala-lang" %% "scala3-compiler" % scalaVersion.value
else
"org.scala-lang" % "scala-compiler" % scalaVersion.value
}

val parserCombinator = "org.scala-lang.modules" %% "scala-parser-combinators" % "1.1.2"
val sbinary = "org.scala-sbt" %% "sbinary" % "0.5.1"
Expand Down
1 change: 1 addition & 0 deletions project/plugins.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ addSbtPlugin("com.eed3si9n" % "sbt-buildinfo" % "0.9.0")
addSbtPlugin("com.typesafe" % "sbt-mima-plugin" % "0.6.1")
addSbtPlugin("com.eed3si9n" % "sbt-projectmatrix" % "0.5.2")
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.10")
addSbtPlugin("ch.epfl.lamp" % "sbt-dotty" % "0.4.4")

0 comments on commit 91e77ea

Please sign in to comment.