Skip to content
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

Add close method to interactive console #875

Merged
merged 2 commits into from
Aug 13, 2020
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
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,10 @@ please check the [CONTRIBUTING guide](CONTRIBUTING.md).

This software is released under the following [LICENSE](LICENSE).

### Note to compiler bridge authors

The compiler bridge classes are loaded using [java.util.ServiceLoader](https://docs.oracle.com/javase/8/docs/api/java/util/ServiceLoader.html). In other words, the class implementing `xsbti.compile.CompilerInterface2` must be mentioned in a file named: `/META-INF/services/xsbti.compile.CompilerInterface2`.

## Acknowledgements

| Logo | Acknowledgement |
Expand Down
3 changes: 2 additions & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ lazy val zincTesting = (projectMatrix in internalPath / "zinc-testing")
name := "zinc Testing",
baseSettings,
noPublish,
libraryDependencies ++= Seq(scalaCheck, scalatest, junit, sjsonnewScalaJson.value)
libraryDependencies ++= Seq(scalaCheck, scalatest, junit, verify, sjsonnewScalaJson.value)
)
.jvmPlatform(scalaVersions = List(scala212, scala213))
.configure(addSbtIO, addSbtUtilLogging)
Expand Down Expand Up @@ -442,6 +442,7 @@ lazy val compilerInterface = (projectMatrix in internalPath / "compiler-interfac
// This is a breaking change
exclude[Problem]("xsbti.compile.CompileOptions.*"),
exclude[Problem]("xsbti.compile.CompileProgress.*"),
exclude[Problem]("xsbti.InteractiveConsoleFactory.createConsole"),
// new API points
exclude[DirectMissingMethodProblem]("xsbti.compile.IncrementalCompiler.compile"),
exclude[ReversedMissingMethodProblem]("xsbti.compile.IncrementalCompiler.compile"),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package sbt
package internal
package inc

import verify._
import sbt.io.IO.withTemporaryDirectory
import sbt.io.syntax._

/** This is a basic test for compiler bridge, mostly wrapped as
* AnalyzingCompiler.
*/
object BasicBridgeSpec
extends BasicTestSuite
with BridgeProviderTestkit
with CompilingSpecification {

test("A compiler bridge should compile") {
withTemporaryDirectory { tempDir =>
compileSrcs(tempDir.toPath, "object Foo")
val t = tempDir / "target" / "Foo$.class"
assert(t.exists)
}
}

test("A compiler bridge should run doc") {
withTemporaryDirectory { tempDir =>
doc(tempDir.toPath)(List())
val t = tempDir / "target" / "index.html"
/// println((tempDir / "target").listFiles.toList)
assert(t.exists)
}
}

test("A compiler bridge should run console") {
withTemporaryDirectory { tempDir =>
console(tempDir.toPath)(":q")
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@ package inc
import java.io.File
import org.scalactic.source.Position

class ClassNameSpecification extends CompilingSpecification {
class ClassNameSpecification
extends UnitSpec
with CompilingSpecification
with BridgeProviderTestkit {

"ClassName" should "create correct binary names for top level object" in {
expectBinaryClassNames("object A", Set("A" -> "A", "A" -> "A$"))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,22 @@ import sbt.io.syntax._
import xsbti.compile._
import sbt.util.Logger
import xsbti.TestCallback.ExtractedClassDependencies
import xsbti.{ ReporterUtil, TestCallback, UseScope, VirtualFile, VirtualFileRef }
import xsbti.{
InteractiveConsoleInterface,
ReporterUtil,
TestCallback,
UseScope,
VirtualFile,
VirtualFileRef
}
import xsbti.api.ClassLike
import xsbti.api.DependencyContext._

/**
* Provides common functionality needed for unit tests that require compiling
* source code using Scala compiler.
*/
trait CompilingSpecification extends BridgeProviderSpecification {
trait CompilingSpecification extends AbstractBridgeProviderTestkit {
def scalaVersion =
sys.props
.get("zinc.build.compilerbridge.scalaVersion")
Expand Down Expand Up @@ -146,6 +153,32 @@ trait CompilingSpecification extends BridgeProviderSpecification {
.toList: _*
)

lazy val emptyChanges: DependencyChanges = new DependencyChanges {
override val modifiedLibraries = new Array[VirtualFileRef](0)
override val modifiedBinaries = new Array[File](0)
override val modifiedClasses = new Array[String](0)
override def isEmpty = true
}

def mkReporter = {
val basicReporterConfig = ReporterUtil.getDefaultReporterConfig()
val reporterConfig = basicReporterConfig.withMaximumErrors(maxErrors)
ReporterManager.getReporter(log, reporterConfig)
}

def mkScalaCompiler(baseDir: Path): (xsbti.compile.ScalaInstance, AnalyzingCompiler) = {
val noLogger = Logger.Null
val compilerBridge = getCompilerBridge(baseDir, noLogger, scalaVersion)
val si = scalaInstance(scalaVersion, baseDir, noLogger)
val sc = scalaCompiler(si, compilerBridge)
(si, sc)
}

def compileSrcs(groupedSrcs: List[List[String]]): (Seq[VirtualFile], TestCallback) =
IO.withTemporaryDirectory { tempDir =>
compileSrcs(tempDir.toPath)(groupedSrcs)
}

/**
* Compiles given source code snippets written to temporary files. Each snippet is
* written to a separate temporary file.
Expand All @@ -157,77 +190,147 @@ trait CompilingSpecification extends BridgeProviderSpecification {
* The sequence of temporary files corresponding to passed snippets and analysis
* callback is returned as a result.
*/
def compileSrcs(groupedSrcs: List[List[String]]): (Seq[VirtualFile], TestCallback) = {
IO.withTemporaryDirectory { tempDir =>
val rootPaths: Map[String, Path] = Map(
"BASE" -> tempDir.toPath,
"SBT_BOOT" -> localBoot,
"JAVA_HOME" -> javaHome
) ++ localCoursierCache
val converter = new MappedFileConverter(rootPaths, false)
val targetDir = tempDir / "target"
val analysisCallback = new TestCallback
targetDir.mkdir()
val files = for {
(compilationUnit, unitId) <- groupedSrcs.zipWithIndex
} yield {
val srcFiles = compilationUnit.zipWithIndex map {
case (src, i) =>
val fileName = s"Test-$unitId-$i.scala"
val f = prepareSrcFile(tempDir, fileName, src)
converter.toVirtualFile(f.toPath)
}
val sources = srcFiles.toArray
val noLogger = Logger.Null
val compilerBridge = getCompilerBridge(tempDir.toPath, noLogger, scalaVersion)
val si = scalaInstance(scalaVersion, tempDir.toPath, noLogger)
val sc = scalaCompiler(si, compilerBridge)
val cp = (si.allJars ++ Array(targetDir)).map(_.toPath)
val emptyChanges: DependencyChanges = new DependencyChanges {
override val modifiedLibraries = new Array[VirtualFileRef](0)
override val modifiedBinaries = new Array[File](0)
override val modifiedClasses = new Array[String](0)
override def isEmpty = true
}
val compArgs = new CompilerArguments(si, sc.classpathOptions)
val arguments = compArgs.makeArguments(Nil, cp, Nil)
val basicReporterConfig = ReporterUtil.getDefaultReporterConfig()
val reporterConfig = basicReporterConfig.withMaximumErrors(maxErrors)
val reporter = ReporterManager.getReporter(log, reporterConfig)
sc.compile(
sources = sources,
converter = converter,
changes = emptyChanges,
options = arguments.toArray,
output = CompileOutput(targetDir.toPath),
callback = analysisCallback,
reporter = reporter,
progressOpt = Optional.empty[CompileProgress],
log = log
)
srcFiles
def compileSrcs(
baseDir: Path
)(groupedSrcs: List[List[String]]): (Seq[VirtualFile], TestCallback) = {
val rootPaths: Map[String, Path] = Map(
"BASE" -> baseDir,
"SBT_BOOT" -> localBoot,
"JAVA_HOME" -> javaHome
) ++ localCoursierCache
val converter = new MappedFileConverter(rootPaths, false)
val targetDir = baseDir / "target"
val analysisCallback = new TestCallback
Files.createDirectory(targetDir)
val files = for {
(compilationUnit, unitId) <- groupedSrcs.zipWithIndex
} yield {
val srcFiles = compilationUnit.zipWithIndex map {
case (content, i) =>
val fileName = s"Test-$unitId-$i.scala"
StringVirtualFile(fileName, content)
}
val sources = srcFiles.toArray[VirtualFile]
val (si, sc) = mkScalaCompiler(baseDir)
val cp = (si.allJars).map(_.toPath) ++ Array(targetDir)
val compArgs = new CompilerArguments(si, sc.classpathOptions)
val arguments = compArgs.makeArguments(Nil, cp, Nil)
val reporter = mkReporter
sc.compile(
sources = sources,
converter = converter,
changes = emptyChanges,
options = arguments.toArray,
output = CompileOutput(targetDir),
callback = analysisCallback,
reporter = reporter,
progressOpt = Optional.empty[CompileProgress],
log = log
)
srcFiles
}

// Make sure that the analysis doesn't lie about the class files that are written
analysisCallback.productClassesToSources.keySet.foreach { classFile =>
if (Files.exists(classFile)) ()
else {
val cfs = Files.list(classFile.getParent).toArray.mkString("\n")
sys.error(s"Class file '${classFile}' doesn't exist! Found:\n$cfs")
}
// Make sure that the analysis doesn't lie about the class files that are written
analysisCallback.productClassesToSources.keySet.foreach { classFile =>
if (Files.exists(classFile)) ()
else {
val cfs = Files.list(classFile.getParent).toArray.mkString("\n")
sys.error(s"Class file '${classFile}' doesn't exist! Found:\n$cfs")
}

(files.flatten map { converter.toVirtualFile(_) }, analysisCallback)
}

(files.flatten map { converter.toVirtualFile(_) }, analysisCallback)
}

def compileSrcs(srcs: String*): (Seq[VirtualFile], TestCallback) = {
compileSrcs(List(srcs.toList))
}

private def prepareSrcFile(baseDir: File, fileName: String, src: String): File = {
val srcFile = new File(baseDir, fileName)
IO.write(srcFile, src)
srcFile
def compileSrcs(baseDir: Path, srcs: String*): (Seq[VirtualFile], TestCallback) = {
compileSrcs(baseDir)(List(srcs.toList))
}

def doc(baseDir: Path)(sourceContents: List[String]): Unit = {
val rootPaths: Map[String, Path] = Map(
"BASE" -> baseDir,
"SBT_BOOT" -> localBoot,
"JAVA_HOME" -> javaHome
) ++ localCoursierCache
val converter = new MappedFileConverter(rootPaths, false)
val targetDir = baseDir / "target"
Files.createDirectory(targetDir)
val (si, sc) = mkScalaCompiler(baseDir)
val reporter = mkReporter
val sources = sourceContents.zipWithIndex map {
case (content, i) =>
val fileName = s"Test-$i.scala"
StringVirtualFile(fileName, content)
}
val cp = (si.allJars).map(_.toPath) ++ Array(targetDir)
val classpath = cp.map(converter.toVirtualFile)
sc.doc(
sources = sources.toArray[VirtualFile],
classpath = classpath,
converter = converter,
outputDirectory = targetDir,
options = Nil,
log = log,
reporter = reporter,
)
()
}

def console(baseDir: Path)(initial: String): Unit = {
val rootPaths: Map[String, Path] = Map(
"BASE" -> baseDir,
"SBT_BOOT" -> localBoot,
"JAVA_HOME" -> javaHome
) ++ localCoursierCache
val converter = new MappedFileConverter(rootPaths, false)
val targetDir = baseDir / "target"
Files.createDirectory(targetDir)
val (si, sc) = mkScalaCompiler(baseDir)
val cp = (si.allJars).map(_.toPath) ++ Array(targetDir)
val classpath = cp.map(converter.toVirtualFile)
sc.console(
classpath = classpath,
converter = converter,
options = Nil,
initialCommands = initial,
cleanupCommands = "",
log = log,
)(None, Nil)
}

def interactiveConsole(baseDir: Path)(args: String*): InteractiveConsoleInterface = {
val rootPaths: Map[String, Path] = Map(
"BASE" -> baseDir,
"SBT_BOOT" -> localBoot,
"JAVA_HOME" -> javaHome
) ++ localCoursierCache
val converter = new MappedFileConverter(rootPaths, false)
val targetDir = baseDir / "target"
Files.createDirectory(targetDir)
val (si, sc) = mkScalaCompiler(baseDir)
val cp = (si.allJars).map(_.toPath) ++ Array(targetDir)
val classpath = cp.map(converter.toVirtualFile)
sc.interactiveConsole(
classpath = classpath,
converter = converter,
options = args.toSeq,
initialCommands = "",
cleanupCommands = "",
log = log,
)(None, Nil)
}

def withInteractiveConsole[A](f: InteractiveConsoleInterface => A): A =
IO.withTemporaryDirectory { tempDir =>
val repl = interactiveConsole(tempDir.toPath)()
try {
f(repl)
} finally {
repl.close()
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ package inc

import xsbti.TestCallback.ExtractedClassDependencies

class DependencySpecification extends CompilingSpecification {
class DependencySpecification
extends UnitSpec
with CompilingSpecification
with BridgeProviderTestkit {

"Dependency phase" should "extract class dependencies from public members" in {
val classDependencies = extractClassDependenciesPublic
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@ package inc
import xsbti.api._
import xsbt.api.SameAPI

class ExtractAPISpecification extends CompilingSpecification {
class ExtractAPISpecification
extends UnitSpec
with CompilingSpecification
with BridgeProviderTestkit {

"ExtractAPI" should "give stable names to members of existential types in method signatures" in stableExistentialNames()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ import java.nio.file.Paths
import org.scalatest.DiagrammedAssertions

class ExtractUsedNamesPerformanceSpecification
extends CompilingSpecification
extends UnitSpec
with CompilingSpecification
with BridgeProviderTestkit
with DiagrammedAssertions {
private def initFileSystem(uri: URI): Option[FileSystem] = {
try Option(FileSystems.getFileSystem(uri))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@ package inc

import org.scalatest.DiagrammedAssertions

class ExtractUsedNamesSpecification extends CompilingSpecification with DiagrammedAssertions {
class ExtractUsedNamesSpecification
extends UnitSpec
with CompilingSpecification
with BridgeProviderTestkit
with DiagrammedAssertions {

"Used names extraction" should "extract imported name" in {
val src = """package a { class A }
Expand Down
Loading