Skip to content

Commit

Permalink
Merge pull request #453 from eed3si9n/wip/213-compiler-bridge
Browse files Browse the repository at this point in the history
"sbt '++ 2.13.0-M2!' compile" does not work with sbt 1.0.0
  • Loading branch information
dwijnand authored Nov 23, 2017
2 parents f7e45b4 + 9d6dfd7 commit d6d6988
Show file tree
Hide file tree
Showing 35 changed files with 387 additions and 34 deletions.
43 changes: 43 additions & 0 deletions bin/bridge213.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#!/bin/bash

# This is a hack to validate the compilation of 2.13 compiler bridge without using sbt,
# which was used for bootstrapping the initial compiler bridge.
# In the future when Scala compiler breaks source compatibility, this script might come in handy.

# $ export SCALA_X_HOME=/usr/local/Cellar/[email protected]/2.13.0-M2

if [[ -z "$SCALA_X_HOME" ]]; then
echo "SCALA_X_HOME is not set!" 1>&2
echo "Run 'export SCALA_X_HOME=/usr/local/Cellar/[email protected]/2.13.0-M2' or equivalent."
exit 1
fi

mkdir -p target/compiler-bridge/

"$SCALA_X_HOME/bin/scalac" \
-nowarn \
-classpath $HOME/.ivy2/cache/org.scala-sbt/compiler-interface/jars/compiler-interface-1.0.3.jar:$HOME/.ivy2/cache/org.scala-sbt/util-interface/jars/util-interface-1.0.2.jar \
-d target/compiler-bridge/ \
internal/compiler-bridge/src/main/scala/xsbt/API.scala \
internal/compiler-bridge/src/main/scala/xsbt/DelegatingReporter.scala \
internal/compiler-bridge/src/main/scala/xsbt/InteractiveConsoleInterface.scala \
internal/compiler-bridge/src/main/scala/xsbt/ScaladocInterface.scala \
internal/compiler-bridge/src/main/scala/xsbt/Analyzer.scala \
internal/compiler-bridge/src/main/scala/xsbt/Dependency.scala \
internal/compiler-bridge/src/main/scala/xsbt/InteractiveConsoleResponse.scala \
internal/compiler-bridge/src/main/scala/xsbt/CallbackGlobal.scala \
internal/compiler-bridge/src/main/scala/xsbt/ExtractAPI.scala \
internal/compiler-bridge/src/main/scala/xsbt/JavaUtils.scala \
internal/compiler-bridge/src/main/scala/xsbt/ClassName.scala \
internal/compiler-bridge/src/main/scala/xsbt/ExtractUsedNames.scala \
internal/compiler-bridge/src/main/scala/xsbt/LocalToNonLocalClass.scala \
internal/compiler-bridge/src/main/scala/xsbt/Command.scala \
internal/compiler-bridge/src/main/scala/xsbt/GlobalHelpers.scala \
internal/compiler-bridge/src/main/scala/xsbt/LocateClassFile.scala \
internal/compiler-bridge/src/main/scala/xsbt/CompilerInterface.scala \
internal/compiler-bridge/src/main/scala/xsbt/InteractiveConsoleFactory.scala \
internal/compiler-bridge/src/main/scala/xsbt/Log.scala \
internal/compiler-bridge/src/main/scala/xsbt/InteractiveConsoleHelper.scala \
internal/compiler-bridge/src/main/scala/xsbt/Message.scala \
internal/compiler-bridge/src/main/scala_2.13/xsbt/Compat.scala \
internal/compiler-bridge/src/main/scala_2.13/xsbt/ConsoleInterface.scala
File renamed without changes.
73 changes: 52 additions & 21 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import Scripted._
def baseVersion = "1.1.0-SNAPSHOT"
def internalPath = file("internal")

lazy val compilerBridgeScalaVersions = List(scala212, scala211, scala210)
lazy val compilerBridgeScalaVersions = List(scala212, scala213, scala211, scala210)
lazy val compilerBridgeTestScalaVersions = List(scala212, scala211, scala210)

def mimaSettings: Seq[Setting[_]] = Seq(
mimaPreviousArtifacts := Set(
Expand Down Expand Up @@ -330,10 +331,17 @@ def wrapIn(color: String, content: String): String = {
else color + content + scala.Console.RESET
}

// Compiler-side interface to compiler that is compiled against the compiler being used either in advance or on the fly.
// Includes API and Analyzer phases that extract source API and relationships.
/**
* Compiler-side interface to compiler that is compiled against the compiler being used either in advance or on the fly.
* Includes API and Analyzer phases that extract source API and relationships.
* As this is essentially implementations of the compiler-interface (per Scala compiler),
* the code here should not be consumed without going through the classloader trick and the interface.
* Due to the hermetic nature of the bridge, there's no necessity to keep binary compatibility across Zinc versions,
* and therefore there's no `mimaSettings` added.
* For the case of Scala 2.13 bridge, we didn't even have the bridge to compare against when Zinc 1.0.0 came out.
*/
lazy val compilerBridge: Project = (project in internalPath / "compiler-bridge")
.dependsOn(compilerInterface % "compile;test->test", zincApiInfo % "test->test")
.dependsOn(compilerInterface)
.settings(
baseSettings,
crossScalaVersions := compilerBridgeScalaVersions,
Expand All @@ -343,17 +351,19 @@ lazy val compilerBridge: Project = (project in internalPath / "compiler-bridge")
// precompiledSettings,
name := "Compiler Bridge",
exportJars := true,
// we need to fork because in unit tests we set usejavacp = true which means
// we are expecting all of our dependencies to be on classpath so Scala compiler
// can use them while constructing its own classpath for compilation
fork in Test := true,
// needed because we fork tests and tests are ran in parallel so we have multiple Scala
// compiler instances that are memory hungry
javaOptions in Test += "-Xmx1G",
inBoth(unmanagedSourceDirectories ++= scalaPartialVersion.value.collect {
case (2, y) if y == 10 => new File(scalaSource.value.getPath + "_2.10")
case (2, y) if y >= 11 => new File(scalaSource.value.getPath + "_2.11+")
case (2, y) if y == 10 => new File(scalaSource.value.getPath + "_2.10")
case (2, y) if y == 11 || y == 12 => new File(scalaSource.value.getPath + "_2.11-12")
case (2, y) if y >= 13 => new File(scalaSource.value.getPath + "_2.13")
}.toList),
// Use a bootstrap compiler bridge to compile the compiler bridge.
scalaCompilerBridgeSource := {
val old = scalaCompilerBridgeSource.value
scalaVersion.value match {
case x if x startsWith "2.13." => ("org.scala-sbt" % "compiler-bridge_2.13.0-M2" % "1.1.0-M1-bootstrap2" % Compile).sources()
case _ => old
}
},
cleanSbtBridge := {
val sbtV = sbtVersion.value
val sbtOrg = "org.scala-sbt"
Expand Down Expand Up @@ -385,7 +395,30 @@ lazy val compilerBridge: Project = (project in internalPath / "compiler-bridge")
},
publishLocal := publishLocal.dependsOn(cleanSbtBridge).value,
altPublishSettings,
mimaSettings,
)

/**
* Tests for the compiler bridge.
* This is split into a separate subproject because testing introduces more dependencies
* (Zinc API Info, which transitively depends on IO).
*/
lazy val compilerBridgeTest = (project in internalPath / "compiler-bridge-test")
.dependsOn(compilerBridge, compilerInterface % "test->test", zincApiInfo % "test->test")
.settings(
name := "Compiler Bridge Test",
baseSettings,
relaxNon212,
// we need to fork because in unit tests we set usejavacp = true which means
// we are expecting all of our dependencies to be on classpath so Scala compiler
// can use them while constructing its own classpath for compilation
fork in Test := true,
// needed because we fork tests and tests are ran in parallel so we have multiple Scala
// compiler instances that are memory hungry
javaOptions in Test += "-Xmx1G",
crossScalaVersions := compilerBridgeTestScalaVersions,
libraryDependencies += scalaCompiler.value,
altPublishSettings,
skip in publish := true,
)

val scalaPartialVersion = Def setting (CrossVersion partialVersion scalaVersion.value)
Expand All @@ -399,7 +432,7 @@ lazy val zincApiInfo = (project in internalPath / "zinc-apiinfo")
.configure(addBaseSettingsAndTestDeps)
.settings(
name := "zinc ApiInfo",
crossScalaVersions := compilerBridgeScalaVersions,
crossScalaVersions := compilerBridgeTestScalaVersions,
relaxNon212,
mimaSettings,
)
Expand All @@ -410,7 +443,7 @@ lazy val zincClasspath = (project in internalPath / "zinc-classpath")
.configure(addBaseSettingsAndTestDeps)
.settings(
name := "zinc Classpath",
crossScalaVersions := compilerBridgeScalaVersions,
crossScalaVersions := compilerBridgeTestScalaVersions,
relaxNon212,
libraryDependencies ++= Seq(scalaCompiler.value, launcherInterface),
mimaSettings,
Expand All @@ -423,7 +456,7 @@ lazy val zincClassfile = (project in internalPath / "zinc-classfile")
.configure(addBaseSettingsAndTestDeps)
.settings(
name := "zinc Classfile",
crossScalaVersions := compilerBridgeScalaVersions,
crossScalaVersions := compilerBridgeTestScalaVersions,
relaxNon212,
mimaSettings,
)
Expand All @@ -441,10 +474,10 @@ lazy val zincScripted = (project in internalPath / "zinc-scripted")

lazy val crossTestBridges = {
Command.command("crossTestBridges") { state =>
(compilerBridgeScalaVersions.flatMap { (bridgeVersion: String) =>
(compilerBridgeTestScalaVersions.flatMap { (bridgeVersion: String) =>
// Note the ! here. You need this so compilerInterface gets forced to the scalaVersion
s"++ $bridgeVersion!" ::
s"${compilerBridge.id}/test" ::
s"${compilerBridgeTest.id}/test" ::
Nil
}) :::
(s"++ $scala212!" ::
Expand All @@ -459,7 +492,6 @@ lazy val publishBridgesAndSet = {
s"${compilerInterface.id}/publishLocal" ::
compilerBridgeScalaVersions.flatMap { (bridgeVersion: String) =>
s"++ $bridgeVersion!" ::
s"${zincApiInfo.id}/publishLocal" ::
s"${compilerBridge.id}/publishLocal" :: Nil
} :::
s"++ $userScalaVersion!" ::
Expand All @@ -475,7 +507,6 @@ lazy val publishBridgesAndTest = Command.args("publishBridgesAndTest", "<version
s"${compilerInterface.id}/publishLocal" ::
(compilerBridgeScalaVersions.flatMap { (bridgeVersion: String) =>
s"++ $bridgeVersion" ::
s"${zincApiInfo.id}/publishLocal" ::
s"${compilerBridge.id}/publishLocal" :: Nil
}) :::
s"++ $version" ::
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@

package xsbt

import scala.tools.nsc.interpreter.IR
import Compat._
import xsbti.InteractiveConsoleResult

object InteractiveConsoleHelper {
implicit def toConsoleResult(ir: IR.Result): InteractiveConsoleResult =
implicit def toConsoleResult(ir: Results.Result): InteractiveConsoleResult =
ir match {
case IR.Success => InteractiveConsoleResult.Success
case IR.Incomplete => InteractiveConsoleResult.Incomplete
case IR.Error => InteractiveConsoleResult.Error
case Results.Success => InteractiveConsoleResult.Success
case Results.Incomplete => InteractiveConsoleResult.Incomplete
case Results.Error => InteractiveConsoleResult.Error
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import scala.tools.nsc.{ GenericRunnerCommand, Settings }

import xsbti.Logger

import Compat._
import InteractiveConsoleHelper._

class InteractiveConsoleInterface(
Expand All @@ -38,9 +39,10 @@ class InteractiveConsoleInterface(
val outWriter: StringWriter = new StringWriter
val poutWriter: PrintWriter = new PrintWriter(outWriter)

val interpreter: IMain = new IMain(compilerSettings, new PrintWriter(outWriter)) {
def lastReq: Request = prevRequestList.last
}
val interpreter: IMain =
new IMain(compilerSettings, replReporter(compilerSettings, new PrintWriter(outWriter))) {
def lastReq: Request = prevRequestList.last
}

def interpret(line: String, synthetic: Boolean): InteractiveConsoleResponse = {
clearBuffer()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package xsbt

import java.io.PrintWriter
import xsbti.compile.Output
import scala.reflect.{ internal => sri }
import scala.reflect.internal.{ util => sriu }
Expand Down Expand Up @@ -150,6 +151,12 @@ trait ZincGlobalCompat {
}

object Compat {
// IR is renamed to Results
val Results = scala.tools.nsc.interpreter.IR

// IMain in 2.13 accepts ReplReporter
def replReporter(settings: Settings, writer: PrintWriter) = writer

implicit final class TreeOps(val tree: sri.Trees#Tree) extends AnyVal {
// Introduced in 2.11
@inline final def hasSymbolField: Boolean = tree.hasSymbol
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,19 @@

package xsbt

import java.io.PrintWriter
import xsbti.compile.Output

import scala.tools.nsc.Settings

abstract class Compat
object Compat
object Compat {
// IR is renamed to Results
val Results = scala.tools.nsc.interpreter.IR

// IMain in 2.13 accepts ReplReporter
def replReporter(settings: Settings, writer: PrintWriter) = writer
}

/** Defines compatibility utils for [[ZincCompiler]]. */
trait ZincGlobalCompat {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
/*
* Zinc - The incremental compiler for Scala.
* Copyright 2011 - 2017, Lightbend, Inc.
* Copyright 2008 - 2010, Mark Harrah
* This software is released under the terms written in LICENSE.
*/

package xsbt

import xsbti.Logger
import scala.tools.nsc.interpreter.{ ILoop, IMain, InteractiveReader, NamedParam }
import scala.tools.nsc.reporters.Reporter
import scala.tools.nsc.{ GenericRunnerCommand, Settings }

class ConsoleInterface {
def commandArguments(
args: Array[String],
bootClasspathString: String,
classpathString: String,
log: Logger
): Array[String] =
MakeSettings.sync(args, bootClasspathString, classpathString, log).recreateArgs.toArray[String]

def run(
args: Array[String],
bootClasspathString: String,
classpathString: String,
initialCommands: String,
cleanupCommands: String,
loader: ClassLoader,
bindNames: Array[String],
bindValues: Array[Any],
log: Logger
): Unit = {
lazy val interpreterSettings = MakeSettings.sync(args.toList, log)
val compilerSettings = MakeSettings.sync(args, bootClasspathString, classpathString, log)

log.info(Message("Starting scala interpreter..."))
log.info(Message(""))

val loop = new ILoop {
override def createInterpreter() = {
if (loader ne null) {
in = InteractiveReader.apply()
intp = new IMain(settings) {
override protected def parentClassLoader =
if (loader eq null) super.parentClassLoader else loader

override protected def newCompiler(settings: Settings, reporter: Reporter) =
super.newCompiler(compilerSettings, reporter)
}
intp.setContextClassLoader()
} else
super.createInterpreter()

for ((id, value) <- bindNames zip bindValues)
intp.quietBind(NamedParam.clazz(id, value))

if (!initialCommands.isEmpty)
intp.interpret(initialCommands)

()
}

override def closeInterpreter(): Unit = {
if (!cleanupCommands.isEmpty)
intp.interpret(cleanupCommands)
super.closeInterpreter()
}
}

loop.process(if (loader eq null) compilerSettings else interpreterSettings)

()
}
}

object MakeSettings {
def apply(args: List[String], log: Logger): Settings = {
val command = new GenericRunnerCommand(args, message => log.error(Message(message)))
if (command.ok)
command.settings
else
throw new InterfaceCompileFailed(Array(), Array(), command.usageMsg)
}

def sync(
args: Array[String],
bootClasspathString: String,
classpathString: String,
log: Logger
): Settings = {
val compilerSettings = sync(args.toList, log)
if (!bootClasspathString.isEmpty)
compilerSettings.bootclasspath.value = bootClasspathString
compilerSettings.classpath.value = classpathString
compilerSettings
}

def sync(options: List[String], log: Logger): Settings = {
val settings = apply(options, log)
settings.Yreplsync.value = true
settings
}
}
Loading

0 comments on commit d6d6988

Please sign in to comment.