mirror of https://github.com/sbt/sbt.git
Port main-actions
This commit is contained in:
parent
781c0317f5
commit
202cd92f0f
|
|
@ -670,6 +670,7 @@ lazy val actionsProj = (project in file("main-actions"))
|
|||
name := "Actions",
|
||||
libraryDependencies += sjsonNewScalaJson.value,
|
||||
libraryDependencies += jline3Terminal,
|
||||
libraryDependencies += eval,
|
||||
mimaSettings,
|
||||
mimaBinaryIssueFilters ++= Seq(
|
||||
// Removed unused private[sbt] nested class
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import java.io.File
|
|||
import java.nio.channels.ClosedChannelException
|
||||
import sbt.internal.inc.{ AnalyzingCompiler, MappedFileConverter, PlainVirtualFile }
|
||||
import sbt.internal.util.{ DeprecatedJLine, Terminal }
|
||||
import sbt.internal.util.Terminal.TerminalOps
|
||||
import sbt.util.Logger
|
||||
import xsbti.compile.{ Compilers, Inputs }
|
||||
|
||||
|
|
@ -78,7 +79,7 @@ final class Console(compiler: AnalyzingCompiler) {
|
|||
terminal.withRawOutput {
|
||||
jline.TerminalFactory.set(terminal.toJLine)
|
||||
DeprecatedJLine.setTerminalOverride(jline3term)
|
||||
terminal.withRawInput(Run.executeSuccess(console0))
|
||||
terminal.withRawInput(Run.executeSuccess(console0()))
|
||||
}
|
||||
} finally {
|
||||
sys.props("scala.color") = previous
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ import java.io.File
|
|||
import java.time.OffsetDateTime
|
||||
import java.util.jar.{ Attributes, Manifest }
|
||||
import scala.collection.JavaConverters._
|
||||
import sbt.internal.util.Types.:+:
|
||||
import sbt.io.IO
|
||||
|
||||
import sjsonnew.JsonFormat
|
||||
|
|
@ -19,8 +18,6 @@ import sjsonnew.JsonFormat
|
|||
import sbt.util.Logger
|
||||
|
||||
import sbt.util.{ CacheStoreFactory, FilesInfo, ModifiedFileInfo, PlainFileInfo }
|
||||
import sbt.internal.util.HNil
|
||||
// import sbt.internal.util.HListFormats._
|
||||
import sbt.util.FileInfo.{ exists, lastModified }
|
||||
import sbt.util.CacheImplicits._
|
||||
import sbt.util.Tracked.{ inputChanged, outputChanged }
|
||||
|
|
@ -139,11 +136,11 @@ object Package {
|
|||
}
|
||||
setVersion(main)
|
||||
|
||||
type Inputs = Seq[(File, String)] :+: FilesInfo[ModifiedFileInfo] :+: Manifest :+: HNil
|
||||
type Inputs = (Seq[(File, String)], FilesInfo[ModifiedFileInfo], Manifest)
|
||||
val cachedMakeJar = inputChanged(cacheStoreFactory make "inputs") {
|
||||
(inChanged, inputs: Inputs) =>
|
||||
import exists.format
|
||||
val sources :+: _ :+: manifest :+: HNil = inputs
|
||||
val (sources, _, manifest) = inputs
|
||||
outputChanged(cacheStoreFactory make "output") { (outChanged, jar: PlainFileInfo) =>
|
||||
if (inChanged || outChanged) {
|
||||
makeJar(sources, jar.file, manifest, log, time)
|
||||
|
|
@ -154,7 +151,7 @@ object Package {
|
|||
}
|
||||
|
||||
val inputFiles = conf.sources.map(_._1).toSet
|
||||
val inputs = conf.sources.distinct :+: lastModified(inputFiles) :+: manifest :+: HNil
|
||||
val inputs = (conf.sources.distinct, lastModified(inputFiles), manifest)
|
||||
cachedMakeJar(inputs)(() => exists(conf.jar))
|
||||
()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,10 +12,6 @@ import java.io.File
|
|||
import sbt.io.syntax._
|
||||
import sbt.io.IO
|
||||
import sbt.internal.inc.{ RawCompiler, ScalaInstance }
|
||||
import sbt.internal.util.Types.:+:
|
||||
// import sbt.internal.util.HListFormats._
|
||||
import sbt.internal.util.HNil
|
||||
import sbt.internal.util.HListFormats._
|
||||
import sbt.util.CacheImplicits._
|
||||
import sbt.util.Tracked.inputChanged
|
||||
import sbt.util.{ CacheStoreFactory, FilesInfo, HashFileInfo, ModifiedFileInfo, PlainFileInfo }
|
||||
|
|
@ -50,12 +46,22 @@ object RawCompileLike {
|
|||
doCompile: Gen
|
||||
): Gen =
|
||||
(sources, classpath, outputDirectory, options, maxErrors, log) => {
|
||||
type Inputs =
|
||||
FilesInfo[HashFileInfo] :+: FilesInfo[ModifiedFileInfo] :+: Seq[File] :+: File :+:
|
||||
Seq[String] :+: Int :+: HNil
|
||||
val inputs: Inputs = hash(sources.toSet ++ optionFiles(options, fileInputOpts)) :+:
|
||||
FilesInfo(classpath.toSet.map(lastModified.fileOrDirectoryMax)) :+: classpath :+:
|
||||
outputDirectory :+: options :+: maxErrors :+: HNil
|
||||
type Inputs = (
|
||||
FilesInfo[HashFileInfo],
|
||||
FilesInfo[ModifiedFileInfo],
|
||||
Seq[File],
|
||||
File,
|
||||
Seq[String],
|
||||
Int,
|
||||
)
|
||||
val inputs: Inputs = (
|
||||
hash(sources.toSet ++ optionFiles(options, fileInputOpts)),
|
||||
FilesInfo[ModifiedFileInfo](classpath.toSet.map(lastModified.fileOrDirectoryMax)),
|
||||
classpath,
|
||||
outputDirectory,
|
||||
options,
|
||||
maxErrors
|
||||
)
|
||||
val cachedComp = inputChanged(cacheStoreFactory make "inputs") { (inChanged, in: Inputs) =>
|
||||
inputChanged(cacheStoreFactory make "output") {
|
||||
(outChanged, outputs: FilesInfo[PlainFileInfo]) =>
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import std._
|
|||
import xsbt.api.{ Discovered, Discovery }
|
||||
import sbt.internal.inc.Analysis
|
||||
import TaskExtra._
|
||||
import sbt.internal.Action
|
||||
import sbt.internal.util.FeedbackProvidedException
|
||||
import xsbti.api.Definition
|
||||
import xsbti.api.ClassLike
|
||||
|
|
@ -430,7 +431,7 @@ object Tests {
|
|||
): Task[Map[String, SuiteResult]] = {
|
||||
val base = Task[(String, (SuiteResult, Seq[TestTask]))](
|
||||
Info[(String, (SuiteResult, Seq[TestTask]))]().setName(name),
|
||||
Pure(() => (name, fun.apply()), `inline` = false)
|
||||
Action.Pure(() => (name, fun.apply()), `inline` = false)
|
||||
)
|
||||
val taggedBase = base.tagw(tags: _*).tag(fun.tags.map(ConcurrentRestrictions.Tag(_)): _*)
|
||||
taggedBase flatMap { case (name, (result, nested)) =>
|
||||
|
|
|
|||
|
|
@ -1,615 +0,0 @@
|
|||
/*
|
||||
* sbt
|
||||
* Copyright 2011 - 2018, Lightbend, Inc.
|
||||
* Copyright 2008 - 2010, Mark Harrah
|
||||
* Licensed under Apache License 2.0 (see LICENSE)
|
||||
*/
|
||||
|
||||
package sbt
|
||||
package compiler
|
||||
|
||||
import scala.collection.mutable.ListBuffer
|
||||
import scala.tools.nsc.{ ast, io, reporters, CompilerCommand, Global, Phase, Settings }
|
||||
import io.{ AbstractFile, PlainFile, VirtualDirectory }
|
||||
import ast.parser.Tokens
|
||||
import reporters.Reporter
|
||||
import scala.reflect.internal.util.{ AbstractFileClassLoader, BatchSourceFile }
|
||||
import Tokens.{ EOF, NEWLINE, NEWLINES, SEMI }
|
||||
import java.io.{ File, FileNotFoundException }
|
||||
import java.nio.ByteBuffer
|
||||
import java.net.URLClassLoader
|
||||
import java.security.MessageDigest
|
||||
import Eval.{ getModule, getValue, WrapValName }
|
||||
|
||||
import sbt.io.{ DirectoryFilter, FileFilter, GlobFilter, Hash, IO, Path }
|
||||
|
||||
// TODO: provide a way to cleanup backing directory
|
||||
|
||||
final class EvalImports(val strings: Seq[(String, Int)], val srcName: String)
|
||||
|
||||
/**
|
||||
* The result of evaluating a Scala expression. The inferred type of the expression is given by `tpe`.
|
||||
* The value may be obtained from `getValue` by providing a parent class loader that provides the classes from the classpath
|
||||
* this expression was compiled against. Each call to `getValue` constructs a new class loader and loads
|
||||
* the module from that class loader. `generated` contains the compiled classes and cache files related
|
||||
* to the expression. The name of the auto-generated module wrapping the expression is `enclosingModule`.
|
||||
*/
|
||||
final class EvalResult(
|
||||
val tpe: String,
|
||||
val getValue: ClassLoader => Any,
|
||||
val generated: Seq[File],
|
||||
val enclosingModule: String
|
||||
)
|
||||
|
||||
/**
|
||||
* The result of evaluating a group of Scala definitions. The definitions are wrapped in an auto-generated,
|
||||
* top-level module named `enclosingModule`. `generated` contains the compiled classes and cache files related to the definitions.
|
||||
* A new class loader containing the module may be obtained from `loader` by passing the parent class loader providing the classes
|
||||
* from the classpath that the definitions were compiled against. The list of vals with the requested types is `valNames`.
|
||||
* The values for these may be obtained by providing the parent class loader to `values` as is done with `loader`.
|
||||
*/
|
||||
final class EvalDefinitions(
|
||||
val loader: ClassLoader => ClassLoader,
|
||||
val generated: Seq[File],
|
||||
val enclosingModule: String,
|
||||
val valNames: Seq[String]
|
||||
) {
|
||||
def values(parent: ClassLoader): Seq[Any] = {
|
||||
val module = getModule(enclosingModule, loader(parent))
|
||||
for (n <- valNames) yield module.getClass.getMethod(n).invoke(module)
|
||||
}
|
||||
}
|
||||
|
||||
final class EvalException(msg: String) extends RuntimeException(msg)
|
||||
// not thread safe, since it reuses a Global instance
|
||||
final class Eval(
|
||||
optionsNoncp: Seq[String],
|
||||
classpath: Seq[File],
|
||||
mkReporter: Settings => EvalReporter,
|
||||
backing: Option[File]
|
||||
) {
|
||||
def this(mkReporter: Settings => EvalReporter, backing: Option[File]) =
|
||||
this(Nil, IO.classLocationPath[Product].toFile :: Nil, mkReporter, backing)
|
||||
def this() = this(EvalReporter.console, None)
|
||||
|
||||
backing.foreach(IO.createDirectory)
|
||||
val classpathString = Path.makeString(classpath ++ backing.toList)
|
||||
val options = "-cp" +: classpathString +: optionsNoncp
|
||||
|
||||
lazy val settings = {
|
||||
val s = new Settings(println)
|
||||
new CompilerCommand(options.toList, s) // this side-effects on Settings..
|
||||
s
|
||||
}
|
||||
private lazy val evalReporter = mkReporter(settings)
|
||||
def reporter: Reporter = evalReporter // kept for binary compatibility
|
||||
/**
|
||||
* Subclass of Global which allows us to mutate currentRun from outside.
|
||||
* See for rationale https://issues.scala-lang.org/browse/SI-8794
|
||||
*/
|
||||
final class EvalGlobal(settings: Settings, reporter: Reporter)
|
||||
extends Global(settings, reporter) {
|
||||
override def currentRun: Run = curRun match {
|
||||
case null => super.currentRun // https://github.com/scala/bug/issues/11381
|
||||
case r => r
|
||||
}
|
||||
var curRun: Run = null
|
||||
}
|
||||
lazy val global: EvalGlobal = new EvalGlobal(settings, evalReporter)
|
||||
import global._
|
||||
|
||||
private[sbt] def unlinkDeferred(): Unit = {
|
||||
toUnlinkLater foreach unlink
|
||||
toUnlinkLater = Nil
|
||||
}
|
||||
|
||||
private[this] var toUnlinkLater = List[Symbol]()
|
||||
private[this] def unlink(sym: Symbol) = sym.owner.info.decls.unlink(sym)
|
||||
|
||||
def eval(
|
||||
expression: String,
|
||||
imports: EvalImports = noImports,
|
||||
tpeName: Option[String] = None,
|
||||
srcName: String = "<setting>",
|
||||
line: Int = DefaultStartLine
|
||||
): EvalResult = {
|
||||
val ev = new EvalType[String] {
|
||||
def sourceName: String = srcName
|
||||
def makeUnit = mkUnit(srcName, line, expression)
|
||||
def unlink = true
|
||||
def unitBody(unit: CompilationUnit, importTrees: Seq[Tree], moduleName: String): Tree = {
|
||||
val (parser, tree) = parse(unit, settingErrorStrings, _.expr())
|
||||
val tpt: Tree = expectedType(tpeName)
|
||||
augment(parser, importTrees, tree, tpt, moduleName)
|
||||
}
|
||||
def extra(run: Run, unit: CompilationUnit) = enteringPhase(run.typerPhase.next) {
|
||||
(new TypeExtractor).getType(unit.body)
|
||||
}
|
||||
def read(file: File) = IO.read(file)
|
||||
def write(value: String, f: File) = IO.write(f, value)
|
||||
def extraHash = ""
|
||||
}
|
||||
val i = evalCommon(expression :: Nil, imports, tpeName, ev)
|
||||
val value = (cl: ClassLoader) => getValue[Any](i.enclosingModule, i.loader(cl))
|
||||
new EvalResult(i.extra, value, i.generated, i.enclosingModule)
|
||||
}
|
||||
def evalDefinitions(
|
||||
definitions: Seq[(String, scala.Range)],
|
||||
imports: EvalImports,
|
||||
srcName: String,
|
||||
file: Option[File],
|
||||
valTypes: Seq[String]
|
||||
): EvalDefinitions = {
|
||||
require(definitions.nonEmpty, "Definitions to evaluate cannot be empty.")
|
||||
val ev = new EvalType[Seq[String]] {
|
||||
lazy val (fullUnit, defUnits) = mkDefsUnit(srcName, definitions)
|
||||
def sourceName: String = srcName
|
||||
def makeUnit = fullUnit
|
||||
def unlink = false
|
||||
def unitBody(unit: CompilationUnit, importTrees: Seq[Tree], moduleName: String): Tree = {
|
||||
val fullParser = new syntaxAnalyzer.UnitParser(unit)
|
||||
val trees = defUnits flatMap parseDefinitions
|
||||
syntheticModule(fullParser, importTrees, trees.toList, moduleName)
|
||||
}
|
||||
def extra(run: Run, unit: CompilationUnit) = {
|
||||
enteringPhase(run.typerPhase.next) {
|
||||
(new ValExtractor(valTypes.toSet)).getVals(unit.body)
|
||||
}
|
||||
}
|
||||
def read(file: File) = IO.readLines(file)
|
||||
def write(value: Seq[String], file: File) = IO.writeLines(file, value)
|
||||
def extraHash = file match {
|
||||
case Some(f) => f.getAbsolutePath
|
||||
case None => ""
|
||||
}
|
||||
}
|
||||
val i: EvalIntermediate[Seq[String]] = evalCommon(definitions.map(_._1), imports, Some(""), ev)
|
||||
new EvalDefinitions(i.loader, i.generated, i.enclosingModule, i.extra.reverse)
|
||||
}
|
||||
|
||||
private[this] def evalCommon[T](
|
||||
content: Seq[String],
|
||||
imports: EvalImports,
|
||||
tpeName: Option[String],
|
||||
ev: EvalType[T]
|
||||
): EvalIntermediate[T] = {
|
||||
import Eval._
|
||||
// TODO - We also encode the source of the setting into the hash to avoid conflicts where the exact SAME setting
|
||||
// is defined in multiple evaluated instances with a backing. This leads to issues with finding a previous
|
||||
// value on the classpath when compiling.
|
||||
|
||||
// This is a hot path.
|
||||
val digester = MessageDigest.getInstance("SHA")
|
||||
content foreach { c =>
|
||||
digester.update(bytes(c))
|
||||
}
|
||||
backing foreach { x =>
|
||||
digester.update(fileExistsBytes(x))
|
||||
}
|
||||
options foreach { o =>
|
||||
digester.update(bytes(o))
|
||||
}
|
||||
classpath foreach { f =>
|
||||
fileModifiedHash(f, digester)
|
||||
}
|
||||
imports.strings.map(_._1) foreach { x =>
|
||||
digester.update(bytes(x))
|
||||
}
|
||||
tpeName foreach { x =>
|
||||
digester.update(bytes(x))
|
||||
}
|
||||
digester.update(bytes(ev.extraHash))
|
||||
val d = digester.digest()
|
||||
|
||||
val hash = Hash.toHex(d)
|
||||
val moduleName = makeModuleName(hash)
|
||||
|
||||
val (extra, loader) =
|
||||
try {
|
||||
backing match {
|
||||
case Some(back) if classExists(back, moduleName) =>
|
||||
val loader = (parent: ClassLoader) =>
|
||||
(new URLClassLoader(Array(back.toURI.toURL), parent): ClassLoader)
|
||||
val extra = ev.read(cacheFile(back, moduleName))
|
||||
(extra, loader)
|
||||
case _ =>
|
||||
compileAndLoad(imports, backing, moduleName, ev)
|
||||
}
|
||||
} finally {
|
||||
// send a final report even if the class file was backed to reset preceding diagnostics
|
||||
evalReporter.finalReport(ev.sourceName)
|
||||
}
|
||||
|
||||
val generatedFiles = getGeneratedFiles(backing, moduleName)
|
||||
new EvalIntermediate(extra, loader, generatedFiles, moduleName)
|
||||
}
|
||||
// location of the cached type or definition information
|
||||
private[this] def cacheFile(base: File, moduleName: String): File =
|
||||
new File(base, moduleName + ".cache")
|
||||
|
||||
private def compileAndLoad[T](
|
||||
imports: EvalImports,
|
||||
backing: Option[File],
|
||||
moduleName: String,
|
||||
ev: EvalType[T]
|
||||
): (T, ClassLoader => ClassLoader) = {
|
||||
evalReporter.reset()
|
||||
val unit = ev.makeUnit
|
||||
val run = new Run {
|
||||
override def units = (unit :: Nil).iterator
|
||||
}
|
||||
try {
|
||||
compileAndLoad(run, unit, imports, backing, moduleName, ev)
|
||||
} finally {
|
||||
// unlink all
|
||||
for ((sym, _) <- run.symSource) if (ev.unlink) unlink(sym) else toUnlinkLater ::= sym
|
||||
}
|
||||
}
|
||||
private[this] def compileAndLoad[T](
|
||||
run: Run,
|
||||
unit: CompilationUnit,
|
||||
imports: EvalImports,
|
||||
backing: Option[File],
|
||||
moduleName: String,
|
||||
ev: EvalType[T]
|
||||
): (T, ClassLoader => ClassLoader) = {
|
||||
global.curRun = run
|
||||
run.currentUnit = unit
|
||||
val dir = outputDirectory(backing)
|
||||
settings.outputDirs setSingleOutput dir
|
||||
|
||||
val importTrees = parseImports(imports)
|
||||
unit.body = ev.unitBody(unit, importTrees, moduleName)
|
||||
|
||||
def compile(phase: Phase): Unit = {
|
||||
globalPhase = phase
|
||||
if (phase == null || phase == phase.next || evalReporter.hasErrors) ()
|
||||
else {
|
||||
enteringPhase(phase) { phase.run() }
|
||||
compile(phase.next)
|
||||
}
|
||||
}
|
||||
|
||||
compile(run.namerPhase)
|
||||
checkError("Type error in expression")
|
||||
|
||||
val extra = ev.extra(run, unit)
|
||||
for (f <- backing) ev.write(extra, cacheFile(f, moduleName))
|
||||
val loader = (parent: ClassLoader) => new AbstractFileClassLoader(dir, parent)
|
||||
(extra, loader)
|
||||
}
|
||||
|
||||
private[this] def expectedType(tpeName: Option[String]): Tree =
|
||||
tpeName match {
|
||||
case Some(tpe) => parseType(tpe)
|
||||
case None => TypeTree(NoType)
|
||||
}
|
||||
|
||||
private[this] def outputDirectory(backing: Option[File]): AbstractFile =
|
||||
backing match {
|
||||
case None => new VirtualDirectory("<virtual>", None); case Some(dir) => new PlainFile(dir)
|
||||
}
|
||||
|
||||
def load(dir: AbstractFile, moduleName: String): ClassLoader => Any =
|
||||
parent => getValue[Any](moduleName, new AbstractFileClassLoader(dir, parent))
|
||||
def loadPlain(dir: File, moduleName: String): ClassLoader => Any =
|
||||
parent => getValue[Any](moduleName, new URLClassLoader(Array(dir.toURI.toURL), parent))
|
||||
|
||||
// wrap tree in object objectName { def WrapValName = <tree> }
|
||||
def augment(
|
||||
parser: global.syntaxAnalyzer.UnitParser,
|
||||
imports: Seq[Tree],
|
||||
tree: Tree,
|
||||
tpt: Tree,
|
||||
objectName: String
|
||||
): Tree = {
|
||||
val method = DefDef(NoMods, newTermName(WrapValName), Nil, Nil, tpt, tree)
|
||||
syntheticModule(parser, imports, method :: Nil, objectName)
|
||||
}
|
||||
private[this] def syntheticModule(
|
||||
parser: global.syntaxAnalyzer.UnitParser,
|
||||
imports: Seq[Tree],
|
||||
definitions: List[Tree],
|
||||
objectName: String
|
||||
): Tree = {
|
||||
val emptyTypeName = nme.EMPTY.toTypeName
|
||||
def emptyPkg = parser.atPos(0, 0, 0) { Ident(nme.EMPTY_PACKAGE_NAME) }
|
||||
def emptyInit = DefDef(
|
||||
NoMods,
|
||||
nme.CONSTRUCTOR,
|
||||
Nil,
|
||||
List(Nil),
|
||||
TypeTree(),
|
||||
Block(
|
||||
List(Apply(Select(Super(This(emptyTypeName), emptyTypeName), nme.CONSTRUCTOR), Nil)),
|
||||
Literal(Constant(()))
|
||||
)
|
||||
)
|
||||
|
||||
def moduleBody =
|
||||
Template(List(gen.scalaAnyRefConstr), noSelfType, (emptyInit: Tree) :: definitions)
|
||||
def moduleDef = ModuleDef(NoMods, newTermName(objectName), moduleBody)
|
||||
parser.makePackaging(0, emptyPkg, (imports :+ (moduleDef: Tree)).toList)
|
||||
}
|
||||
|
||||
private[this] final class TypeExtractor extends Traverser {
|
||||
private[this] var result = ""
|
||||
def getType(t: Tree) = { result = ""; traverse(t); result }
|
||||
override def traverse(tree: Tree): Unit = tree match {
|
||||
case d: DefDef if d.symbol.nameString == WrapValName =>
|
||||
result = d.symbol.tpe.finalResultType.toString
|
||||
case _ => super.traverse(tree)
|
||||
}
|
||||
}
|
||||
|
||||
/** Tree traverser that obtains the names of vals in a top-level module whose type is a subtype of one of `types`. */
|
||||
private[this] final class ValExtractor(tpes: Set[String]) extends Traverser {
|
||||
private[this] var vals = List[String]()
|
||||
def getVals(t: Tree): List[String] = { vals = Nil; traverse(t); vals }
|
||||
def isAcceptableType(tpe: Type): Boolean = {
|
||||
tpe.baseClasses.exists { sym =>
|
||||
tpes.contains(sym.fullName)
|
||||
}
|
||||
}
|
||||
override def traverse(tree: Tree): Unit = tree match {
|
||||
case ValDef(_, n, actualTpe, _)
|
||||
if isTopLevelModule(tree.symbol.owner) && isAcceptableType(actualTpe.tpe) =>
|
||||
vals ::= n.dropLocal.encoded
|
||||
case _ => super.traverse(tree)
|
||||
}
|
||||
}
|
||||
// inlined implemented of Symbol.isTopLevelModule that was removed in e5b050814deb2e7e1d6d05511d3a6cb6b013b549
|
||||
private[this] def isTopLevelModule(s: Symbol): Boolean =
|
||||
s.hasFlag(reflect.internal.Flags.MODULE) && s.owner.isPackageClass
|
||||
|
||||
private[this] final class EvalIntermediate[T](
|
||||
val extra: T,
|
||||
val loader: ClassLoader => ClassLoader,
|
||||
val generated: Seq[File],
|
||||
val enclosingModule: String
|
||||
)
|
||||
|
||||
private[this] def classExists(dir: File, name: String) = (new File(dir, name + ".class")).exists
|
||||
// TODO: use the code from Analyzer
|
||||
private[this] def getGeneratedFiles(backing: Option[File], moduleName: String): Seq[File] =
|
||||
backing match {
|
||||
case None => Nil
|
||||
case Some(dir) => dir listFiles moduleFileFilter(moduleName)
|
||||
}
|
||||
private[this] def moduleFileFilter(moduleName: String) = new java.io.FilenameFilter {
|
||||
def accept(dir: File, s: String) =
|
||||
(s contains moduleName)
|
||||
}
|
||||
|
||||
private[this] class ParseErrorStrings(
|
||||
val base: String,
|
||||
val extraBlank: String,
|
||||
val missingBlank: String,
|
||||
val extraSemi: String
|
||||
)
|
||||
private[this] def definitionErrorStrings = new ParseErrorStrings(
|
||||
base = "Error parsing definition.",
|
||||
extraBlank = " Ensure that there are no blank lines within a definition.",
|
||||
missingBlank = " Ensure that definitions are separated by blank lines.",
|
||||
extraSemi = " A trailing semicolon is not permitted for standalone definitions."
|
||||
)
|
||||
private[this] def settingErrorStrings = new ParseErrorStrings(
|
||||
base = "Error parsing expression.",
|
||||
extraBlank = " Ensure that there are no blank lines within a setting.",
|
||||
missingBlank = " Ensure that settings are separated by blank lines.",
|
||||
extraSemi =
|
||||
" Note that settings are expressions and do not end with semicolons. (Semicolons are fine within {} blocks, however.)"
|
||||
)
|
||||
|
||||
/**
|
||||
* Parses the provided compilation `unit` according to `f` and then performs checks on the final parser state
|
||||
* to catch errors that are common when the content is embedded in a blank-line-delimited format.
|
||||
*/
|
||||
private[this] def parse[T](
|
||||
unit: CompilationUnit,
|
||||
errors: ParseErrorStrings,
|
||||
f: syntaxAnalyzer.UnitParser => T
|
||||
): (syntaxAnalyzer.UnitParser, T) = {
|
||||
val parser = new syntaxAnalyzer.UnitParser(unit)
|
||||
|
||||
val tree = f(parser)
|
||||
val extra = parser.in.token match {
|
||||
case EOF => errors.extraBlank
|
||||
case _ => ""
|
||||
}
|
||||
checkError(errors.base + extra)
|
||||
|
||||
parser.accept(EOF)
|
||||
val extra2 = parser.in.token match {
|
||||
case SEMI => errors.extraSemi
|
||||
case NEWLINE | NEWLINES => errors.missingBlank
|
||||
case _ => ""
|
||||
}
|
||||
checkError(errors.base + extra2)
|
||||
|
||||
(parser, tree)
|
||||
}
|
||||
private[this] def parseType(tpe: String): Tree = {
|
||||
val tpeParser = new syntaxAnalyzer.UnitParser(mkUnit("<expected-type>", DefaultStartLine, tpe))
|
||||
val tpt0: Tree = tpeParser.typ()
|
||||
tpeParser.accept(EOF)
|
||||
checkError("Error parsing expression type.")
|
||||
tpt0
|
||||
}
|
||||
private[this] def parseImports(imports: EvalImports): Seq[Tree] =
|
||||
imports.strings flatMap { case (s, line) => parseImport(mkUnit(imports.srcName, line, s)) }
|
||||
private[this] def parseImport(importUnit: CompilationUnit): Seq[Tree] = {
|
||||
val parser = new syntaxAnalyzer.UnitParser(importUnit)
|
||||
val trees: Seq[Tree] = parser.importClause()
|
||||
parser.accept(EOF)
|
||||
checkError("Error parsing imports for expression.")
|
||||
trees
|
||||
}
|
||||
private[this] def parseDefinitions(du: CompilationUnit): Seq[Tree] =
|
||||
parse(du, definitionErrorStrings, parseDefinitions)._2
|
||||
|
||||
/** Parses one or more definitions (defs, vals, lazy vals, classes, traits, modules). */
|
||||
private[this] def parseDefinitions(parser: syntaxAnalyzer.UnitParser): Seq[Tree] = {
|
||||
val defs = ListBuffer[Tree]()
|
||||
def run(): Unit =
|
||||
defs ++= parser.nonLocalDefOrDcl
|
||||
parser.acceptStatSepOpt()
|
||||
|
||||
run()
|
||||
while !parser.isStatSeqEnd do run()
|
||||
defs.toList
|
||||
}
|
||||
|
||||
private[this] trait EvalType[T] {
|
||||
|
||||
/** Extracts additional information after the compilation unit is evaluated. */
|
||||
def extra(run: Run, unit: CompilationUnit): T
|
||||
|
||||
/** Deserializes the extra information for unchanged inputs from a cache file. */
|
||||
def read(file: File): T
|
||||
|
||||
/** Serializes the extra information to a cache file, where it can be `read` back if inputs haven't changed. */
|
||||
def write(value: T, file: File): Unit
|
||||
|
||||
def sourceName: String
|
||||
|
||||
/**
|
||||
* Constructs the full compilation unit for this evaluation.
|
||||
* This is used for error reporting during compilation.
|
||||
* The `unitBody` method actually does the parsing and may parse the Tree from another source.
|
||||
*/
|
||||
def makeUnit: CompilationUnit
|
||||
|
||||
/** If true, all top-level symbols from this evaluation will be unlinked. */
|
||||
def unlink: Boolean
|
||||
|
||||
/**
|
||||
* Constructs the Tree to be compiled. The full compilation `unit` from `makeUnit` is provided along with the
|
||||
* parsed imports `importTrees` to be used. `moduleName` should be name of the enclosing module.
|
||||
* The Tree doesn't need to be parsed from the contents of `unit`.
|
||||
*/
|
||||
def unitBody(unit: CompilationUnit, importTrees: Seq[Tree], moduleName: String): Tree
|
||||
|
||||
/** Extra information to include in the hash'd object name to help avoid collisions. */
|
||||
def extraHash: String
|
||||
}
|
||||
|
||||
val DefaultStartLine = 0
|
||||
private[this] def makeModuleName(hash: String): String = "$" + Hash.halve(hash)
|
||||
private[this] def noImports = new EvalImports(Nil, "")
|
||||
private[this] def mkUnit(srcName: String, firstLine: Int, s: String) =
|
||||
new CompilationUnit(new EvalSourceFile(srcName, firstLine, s))
|
||||
private[this] def checkError(label: String) =
|
||||
if (evalReporter.hasErrors) throw new EvalException(label)
|
||||
|
||||
private[this] final class EvalSourceFile(name: String, startLine: Int, contents: String)
|
||||
extends BatchSourceFile(name, contents) {
|
||||
override def lineToOffset(line: Int): Int = super.lineToOffset((line - startLine) max 0)
|
||||
override def offsetToLine(offset: Int): Int = super.offsetToLine(offset) + startLine
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a CompilationUnit for each definition, which can be used to independently parse the definition into a Tree.
|
||||
* Additionally, a CompilationUnit for the combined definitions is constructed for use by combined compilation after parsing.
|
||||
*/
|
||||
private[this] def mkDefsUnit(
|
||||
srcName: String,
|
||||
definitions: Seq[(String, scala.Range)]
|
||||
): (CompilationUnit, Seq[CompilationUnit]) = {
|
||||
def fragmentUnit(content: String, lineMap: Array[Int]) =
|
||||
new CompilationUnit(fragmentSourceFile(srcName, content, lineMap))
|
||||
|
||||
import collection.mutable.ListBuffer
|
||||
val lines = new ListBuffer[Int]()
|
||||
val defs = new ListBuffer[CompilationUnit]()
|
||||
val fullContent = new java.lang.StringBuilder()
|
||||
for ((defString, range) <- definitions) {
|
||||
defs += fragmentUnit(defString, range.toArray)
|
||||
fullContent.append(defString)
|
||||
lines ++= range
|
||||
fullContent.append("\n\n")
|
||||
lines ++= (range.end :: range.end :: Nil)
|
||||
}
|
||||
val fullUnit = fragmentUnit(fullContent.toString, lines.toArray)
|
||||
(fullUnit, defs.toSeq)
|
||||
}
|
||||
|
||||
/**
|
||||
* Source file that can map the offset in the file to and from line numbers that may discontinuous.
|
||||
* The values in `lineMap` must be ordered, but need not be consecutive.
|
||||
*/
|
||||
private[this] def fragmentSourceFile(srcName: String, content: String, lineMap: Array[Int]) =
|
||||
new BatchSourceFile(srcName, content) {
|
||||
override def lineToOffset(line: Int): Int =
|
||||
super.lineToOffset(lineMap.indexWhere(_ == line) max 0)
|
||||
override def offsetToLine(offset: Int): Int = index(lineMap, super.offsetToLine(offset))
|
||||
// the SourceFile attribute is populated from this method, so we are required to only return the name
|
||||
override def toString = new File(srcName).getName
|
||||
private[this] def index(a: Array[Int], i: Int): Int = if (i < 0 || i >= a.length) 0 else a(i)
|
||||
}
|
||||
}
|
||||
private[sbt] object Eval {
|
||||
def optBytes[T](o: Option[T])(f: T => Array[Byte]): Array[Byte] = seqBytes(o.toSeq)(f)
|
||||
def stringSeqBytes(s: Seq[String]): Array[Byte] = seqBytes(s)(bytes)
|
||||
def seqBytes[T](s: Seq[T])(f: T => Array[Byte]): Array[Byte] = bytes(s map f)
|
||||
def bytes(b: Seq[Array[Byte]]): Array[Byte] = bytes(b.length) ++ b.flatten.toArray[Byte]
|
||||
def bytes(b: Boolean): Array[Byte] = Array[Byte](if (b) 1 else 0)
|
||||
|
||||
// fileModifiedBytes is a hot method, taking up 0.85% of reload time
|
||||
// This is a procedural version
|
||||
def fileModifiedHash(f: File, digester: MessageDigest): Unit = {
|
||||
if (f.isDirectory)
|
||||
(f listFiles classDirFilter) foreach { x =>
|
||||
fileModifiedHash(x, digester)
|
||||
}
|
||||
else digester.update(bytes(getModifiedTimeOrZero(f)))
|
||||
|
||||
digester.update(bytes(f.getAbsolutePath))
|
||||
}
|
||||
|
||||
// This uses NIO instead of the JNA-based IO.getModifiedTimeOrZero for speed
|
||||
def getModifiedTimeOrZero(f: File): Long =
|
||||
try {
|
||||
sbt.io.JavaMilli.getModifiedTime(f.getPath)
|
||||
} catch {
|
||||
case _: FileNotFoundException => 0L
|
||||
}
|
||||
|
||||
def fileExistsBytes(f: File): Array[Byte] =
|
||||
bytes(f.exists) ++
|
||||
bytes(f.getAbsolutePath)
|
||||
|
||||
def bytes(s: String): Array[Byte] = s getBytes "UTF-8"
|
||||
def bytes(l: Long): Array[Byte] = {
|
||||
val buffer = ByteBuffer.allocate(8)
|
||||
buffer.putLong(l)
|
||||
buffer.array
|
||||
}
|
||||
def bytes(i: Int): Array[Byte] = {
|
||||
val buffer = ByteBuffer.allocate(4)
|
||||
buffer.putInt(i)
|
||||
buffer.array
|
||||
}
|
||||
|
||||
/** The name of the synthetic val in the synthetic module that an expression is assigned to. */
|
||||
final val WrapValName = "$sbtdef"
|
||||
|
||||
/**
|
||||
* Gets the value of the expression wrapped in module `objectName`, which is accessible via `loader`.
|
||||
* The module name should not include the trailing `$`.
|
||||
*/
|
||||
def getValue[T](objectName: String, loader: ClassLoader): T = {
|
||||
val module = getModule(objectName, loader)
|
||||
val accessor = module.getClass.getMethod(WrapValName)
|
||||
val value = accessor.invoke(module)
|
||||
value.asInstanceOf[T]
|
||||
}
|
||||
|
||||
/** Gets the top-level module `moduleName` from the provided class `loader`. The module name should not include the trailing `$`. */
|
||||
def getModule(moduleName: String, loader: ClassLoader): Any = {
|
||||
val clazz = Class.forName(moduleName + "$", true, loader)
|
||||
clazz.getField("MODULE$").get(null)
|
||||
}
|
||||
|
||||
private val classDirFilter: FileFilter = DirectoryFilter || GlobFilter("*.class")
|
||||
}
|
||||
|
|
@ -1,62 +0,0 @@
|
|||
/*
|
||||
* sbt
|
||||
* Copyright 2011 - 2018, Lightbend, Inc.
|
||||
* Copyright 2008 - 2010, Mark Harrah
|
||||
* Licensed under Apache License 2.0 (see LICENSE)
|
||||
*/
|
||||
|
||||
package sbt.compiler
|
||||
|
||||
import scala.reflect.internal.settings.MutableSettings
|
||||
import scala.reflect.internal.util.Position
|
||||
import scala.tools.nsc.Settings
|
||||
import scala.tools.nsc.reporters.{ ConsoleReporter, FilteringReporter }
|
||||
|
||||
/**
|
||||
* Reporter used to compile *.sbt files that forwards compiler diagnostics to BSP clients
|
||||
*/
|
||||
abstract class EvalReporter extends FilteringReporter {
|
||||
|
||||
/**
|
||||
* Send a final report to clear out the outdated diagnostics.
|
||||
* @param sourceName a *.sbt file
|
||||
*/
|
||||
def finalReport(sourceName: String): Unit
|
||||
}
|
||||
|
||||
object EvalReporter {
|
||||
def console(s: Settings): EvalReporter = new ForwardingReporter(new ConsoleReporter(s))
|
||||
}
|
||||
|
||||
class ForwardingReporter(delegate: FilteringReporter) extends EvalReporter {
|
||||
def settings: Settings = delegate.settings
|
||||
|
||||
def doReport(pos: Position, msg: String, severity: Severity): Unit =
|
||||
delegate.doReport(pos, msg, severity)
|
||||
|
||||
override def filter(pos: Position, msg: String, severity: Severity): Int =
|
||||
delegate.filter(pos, msg, severity)
|
||||
|
||||
override def increment(severity: Severity): Unit = delegate.increment(severity)
|
||||
|
||||
override def errorCount: Int = delegate.errorCount
|
||||
override def warningCount: Int = delegate.warningCount
|
||||
|
||||
override def hasErrors: Boolean = delegate.hasErrors
|
||||
override def hasWarnings: Boolean = delegate.hasWarnings
|
||||
|
||||
override def comment(pos: Position, msg: String): Unit = delegate.comment(pos, msg)
|
||||
|
||||
override def cancelled: Boolean = delegate.cancelled
|
||||
override def cancelled_=(b: Boolean): Unit = delegate.cancelled_=(b)
|
||||
|
||||
override def flush(): Unit = delegate.flush()
|
||||
override def finish(): Unit = delegate.finish()
|
||||
override def reset(): Unit =
|
||||
delegate.reset() // super.reset not necessary, own state is never modified
|
||||
|
||||
override def rerunWithDetails(setting: MutableSettings#Setting, name: String): String =
|
||||
delegate.rerunWithDetails(setting, name)
|
||||
|
||||
override def finalReport(sourceName: String): Unit = ()
|
||||
}
|
||||
|
|
@ -1,114 +0,0 @@
|
|||
/*
|
||||
* sbt
|
||||
* Copyright 2011 - 2018, Lightbend, Inc.
|
||||
* Copyright 2008 - 2010, Mark Harrah
|
||||
* Licensed under Apache License 2.0 (see LICENSE)
|
||||
*/
|
||||
|
||||
package sbt
|
||||
package compiler
|
||||
|
||||
import scala.language.reflectiveCalls
|
||||
import org.scalacheck._
|
||||
import Prop._
|
||||
import scala.tools.nsc.Settings
|
||||
import scala.tools.nsc.reporters.StoreReporter
|
||||
|
||||
import sbt.io.IO
|
||||
|
||||
class EvalTest extends Properties("eval") {
|
||||
private[this] lazy val reporter = new StoreReporter(new Settings())
|
||||
import reporter.ERROR
|
||||
private[this] lazy val eval = new Eval(_ => new ForwardingReporter(reporter), None)
|
||||
|
||||
property("inferred integer") = forAll { (i: Int) =>
|
||||
val result = eval.eval(i.toString)
|
||||
(label("Value", value(result)) |: (value(result) == i)) &&
|
||||
(label("Type", value(result)) |: (result.tpe == IntType)) &&
|
||||
(label("Files", result.generated) |: (result.generated.isEmpty))
|
||||
}
|
||||
|
||||
property("explicit integer") = forAll { (i: Int) =>
|
||||
val result = eval.eval(i.toString, tpeName = Some(IntType))
|
||||
(label("Value", value(result)) |: (value(result) == i)) &&
|
||||
(label("Type", result.tpe) |: (result.tpe == IntType)) &&
|
||||
(label("Files", result.generated) |: (result.generated.isEmpty))
|
||||
}
|
||||
|
||||
property("type mismatch") = forAll { (i: Int, l: Int) =>
|
||||
val line = math.abs(l)
|
||||
val src = "mismatch"
|
||||
throws(classOf[RuntimeException])(
|
||||
eval.eval(i.toString, tpeName = Some(BooleanType), line = line, srcName = src)
|
||||
) &&
|
||||
hasErrors(line + 1, src)
|
||||
}
|
||||
|
||||
property("backed local class") = forAll { (i: Int) =>
|
||||
IO.withTemporaryDirectory { dir =>
|
||||
val eval = new Eval(_ => new ForwardingReporter(reporter), backing = Some(dir))
|
||||
val result = eval.eval(local(i))
|
||||
val v = value(result).asInstanceOf[{ def i: Int }].i
|
||||
(label("Value", v) |: (v == i)) &&
|
||||
(label("Type", result.tpe) |: (result.tpe == LocalType)) &&
|
||||
(label("Files", result.generated) |: result.generated.nonEmpty)
|
||||
}
|
||||
}
|
||||
|
||||
val ValTestNames = Set("x", "a")
|
||||
val ValTestContent = """
|
||||
val x: Int = {
|
||||
val y: Int = 4
|
||||
y
|
||||
}
|
||||
val z: Double = 3.0
|
||||
val a = 9
|
||||
val p = {
|
||||
object B { val i = 3 }
|
||||
class C { val j = 4 }
|
||||
"asdf"
|
||||
}
|
||||
"""
|
||||
|
||||
property("val test") = secure {
|
||||
val defs = (ValTestContent, 1 to 7) :: Nil
|
||||
val res =
|
||||
eval.evalDefinitions(defs, new EvalImports(Nil, ""), "<defs>", None, "scala.Int" :: Nil)
|
||||
label("Val names", res.valNames) |: (res.valNames.toSet == ValTestNames)
|
||||
}
|
||||
|
||||
property("explicit import") = forAll(testImport("import math.abs" :: Nil))
|
||||
property("wildcard import") = forAll(testImport("import math._" :: Nil))
|
||||
property("comma-separated imports") = forAll(
|
||||
testImport("import annotation._, math._, meta._" :: Nil)
|
||||
)
|
||||
property("multiple imports") = forAll(
|
||||
testImport("import annotation._" :: "import math._" :: "import meta._" :: Nil)
|
||||
)
|
||||
|
||||
private[this] def testImport(imports: Seq[String]): Int => Prop =
|
||||
i =>
|
||||
value(eval.eval("abs(" + i + ")", new EvalImports(imports.zipWithIndex, "imp"))) == math.abs(
|
||||
i
|
||||
)
|
||||
|
||||
private[this] def local(i: Int) = "{ class ETest(val i: Int); new ETest(" + i + ") }"
|
||||
val LocalType = "AnyRef{val i: Int}"
|
||||
|
||||
private[this] def value(r: EvalResult) = r.getValue(getClass.getClassLoader)
|
||||
private[this] def hasErrors(line: Int, src: String) = {
|
||||
val is = reporter.infos
|
||||
("Has errors" |: is.nonEmpty) &&
|
||||
all(is.toSeq.map(validPosition(line, src)): _*)
|
||||
}
|
||||
private[this] def validPosition(line: Int, src: String)(i: StoreReporter.Info) = {
|
||||
val nme = i.pos.source.file.name
|
||||
(label("Severity", i.severity) |: (i.severity == ERROR)) &&
|
||||
(label("Line", i.pos.line) |: (i.pos.line == line)) &&
|
||||
(label("Name", nme) |: (nme == src))
|
||||
}
|
||||
val IntType = "Int"
|
||||
val BooleanType = "Boolean"
|
||||
|
||||
def label(s: String, value: Any) = s + " (" + value + ")"
|
||||
}
|
||||
|
|
@ -6,7 +6,7 @@ object Dependencies {
|
|||
// WARNING: Please Scala update versions in PluginCross.scala too
|
||||
val scala212 = "2.12.17"
|
||||
val scala213 = "2.13.8"
|
||||
val scala3 = "3.1.0"
|
||||
val scala3 = "3.1.1"
|
||||
val checkPluginCross = settingKey[Unit]("Make sure scalaVersion match up")
|
||||
val baseScalaVersion = scala3
|
||||
def nightlyVersion: Option[String] =
|
||||
|
|
@ -89,6 +89,8 @@ object Dependencies {
|
|||
val sjsonNewMurmurhash = sjsonNew("sjson-new-murmurhash")
|
||||
val sjsonNewCore = sjsonNew("sjson-new-core")
|
||||
|
||||
val eval = ("com.eed3si9n.eval" % "eval" % "0.1.0").cross(CrossVersion.full)
|
||||
|
||||
// JLine 3 version must be coordinated together with JAnsi version
|
||||
// and the JLine 2 fork version, which uses the same JAnsi
|
||||
val jline =
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ private final case class FileHashModifiedArrayRepr(
|
|||
lastModified: Long
|
||||
) extends HashModifiedFileInfo
|
||||
|
||||
final case class FilesInfo[F <: FileInfo] private[util] (files: Set[F])
|
||||
final case class FilesInfo[F <: FileInfo] private[sbt] (files: Set[F])
|
||||
object FilesInfo {
|
||||
def empty[F <: FileInfo]: FilesInfo[F] = FilesInfo(Set.empty[F])
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue