mirror of https://github.com/sbt/sbt.git
Merge branch 'develop' into bspStartTry
This commit is contained in:
commit
0be658d299
|
|
@ -164,7 +164,10 @@ def mimaSettingsSince(versions: Seq[String]): Seq[Def.Setting[_]] = Def settings
|
|||
exclude[FinalClassProblem]("sbt.internal.*"),
|
||||
exclude[FinalMethodProblem]("sbt.internal.*"),
|
||||
exclude[IncompatibleResultTypeProblem]("sbt.internal.*"),
|
||||
exclude[ReversedMissingMethodProblem]("sbt.internal.*")
|
||||
exclude[ReversedMissingMethodProblem]("sbt.internal.*"),
|
||||
exclude[DirectMissingMethodProblem]("sbt.PluginData.apply"),
|
||||
exclude[DirectMissingMethodProblem]("sbt.PluginData.copy"),
|
||||
exclude[DirectMissingMethodProblem]("sbt.PluginData.this"),
|
||||
),
|
||||
)
|
||||
|
||||
|
|
@ -678,6 +681,8 @@ lazy val actionsProj = (project in file("main-actions"))
|
|||
exclude[DirectMissingMethodProblem]("sbt.compiler.Eval.filesModifiedBytes"),
|
||||
exclude[DirectMissingMethodProblem]("sbt.compiler.Eval.fileModifiedBytes"),
|
||||
exclude[DirectMissingMethodProblem]("sbt.Doc.$init$"),
|
||||
// Added field in nested private[this] class
|
||||
exclude[ReversedMissingMethodProblem]("sbt.compiler.Eval#EvalType.sourceName"),
|
||||
),
|
||||
)
|
||||
.configure(
|
||||
|
|
|
|||
|
|
@ -12,8 +12,6 @@
|
|||
|
||||
[repositories]
|
||||
local
|
||||
local-preloaded-ivy: file:///${sbt.preloaded-${sbt.global.base-${user.home}/.sbt}/preloaded/}, [organization]/[module]/[revision]/[type]s/[artifact](-[classifier]).[ext]
|
||||
local-preloaded: file:///${sbt.preloaded-${sbt.global.base-${user.home}/.sbt}/preloaded/}
|
||||
maven-central
|
||||
sbt-maven-releases: https://repo.scala-sbt.org/scalasbt/maven-releases/, bootOnly
|
||||
sbt-maven-snapshots: https://repo.scala-sbt.org/scalasbt/maven-snapshots/, bootOnly
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ 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.{ ConsoleReporter, Reporter }
|
||||
import reporters.Reporter
|
||||
import scala.reflect.internal.util.{ AbstractFileClassLoader, BatchSourceFile }
|
||||
import Tokens.{ EOF, NEWLINE, NEWLINES, SEMI }
|
||||
import java.io.{ File, FileNotFoundException }
|
||||
|
|
@ -65,12 +65,12 @@ final class EvalException(msg: String) extends RuntimeException(msg)
|
|||
final class Eval(
|
||||
optionsNoncp: Seq[String],
|
||||
classpath: Seq[File],
|
||||
mkReporter: Settings => Reporter,
|
||||
mkReporter: Settings => EvalReporter,
|
||||
backing: Option[File]
|
||||
) {
|
||||
def this(mkReporter: Settings => Reporter, backing: Option[File]) =
|
||||
def this(mkReporter: Settings => EvalReporter, backing: Option[File]) =
|
||||
this(Nil, IO.classLocationPath[Product].toFile :: Nil, mkReporter, backing)
|
||||
def this() = this(s => new ConsoleReporter(s), None)
|
||||
def this() = this(EvalReporter.console, None)
|
||||
|
||||
backing.foreach(IO.createDirectory)
|
||||
val classpathString = Path.makeString(classpath ++ backing.toList)
|
||||
|
|
@ -81,8 +81,8 @@ final class Eval(
|
|||
new CompilerCommand(options.toList, s) // this side-effects on Settings..
|
||||
s
|
||||
}
|
||||
lazy val reporter = mkReporter(settings)
|
||||
|
||||
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
|
||||
|
|
@ -95,7 +95,7 @@ final class Eval(
|
|||
}
|
||||
var curRun: Run = null
|
||||
}
|
||||
lazy val global: EvalGlobal = new EvalGlobal(settings, reporter)
|
||||
lazy val global: EvalGlobal = new EvalGlobal(settings, evalReporter)
|
||||
import global._
|
||||
|
||||
private[sbt] def unlinkDeferred(): Unit = {
|
||||
|
|
@ -114,6 +114,7 @@ final class Eval(
|
|||
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 = {
|
||||
|
|
@ -142,6 +143,7 @@ final class Eval(
|
|||
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 = {
|
||||
|
|
@ -202,28 +204,19 @@ final class Eval(
|
|||
val hash = Hash.toHex(d)
|
||||
val moduleName = makeModuleName(hash)
|
||||
|
||||
lazy val unit = {
|
||||
reporter.reset
|
||||
ev.makeUnit
|
||||
}
|
||||
lazy val run = new Run {
|
||||
override def units = (unit :: Nil).iterator
|
||||
}
|
||||
def unlinkAll(): Unit =
|
||||
for ((sym, _) <- run.symSource) if (ev.unlink) unlink(sym) else toUnlinkLater ::= sym
|
||||
|
||||
val (extra, loader) = 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 _ =>
|
||||
try {
|
||||
compileAndLoad(run, unit, imports, backing, moduleName, ev)
|
||||
} finally {
|
||||
unlinkAll()
|
||||
}
|
||||
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)
|
||||
|
|
@ -232,6 +225,25 @@ final class Eval(
|
|||
// 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,
|
||||
|
|
@ -250,7 +262,7 @@ final class Eval(
|
|||
|
||||
def compile(phase: Phase): Unit = {
|
||||
globalPhase = phase
|
||||
if (phase == null || phase == phase.next || reporter.hasErrors)
|
||||
if (phase == null || phase == phase.next || evalReporter.hasErrors)
|
||||
()
|
||||
else {
|
||||
enteringPhase(phase) { phase.run }
|
||||
|
|
@ -457,6 +469,8 @@ final class Eval(
|
|||
/** 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.
|
||||
|
|
@ -484,7 +498,7 @@ final class Eval(
|
|||
private[this] def mkUnit(srcName: String, firstLine: Int, s: String) =
|
||||
new CompilationUnit(new EvalSourceFile(srcName, firstLine, s))
|
||||
private[this] def checkError(label: String) =
|
||||
if (reporter.hasErrors) throw new EvalException(label)
|
||||
if (evalReporter.hasErrors) throw new EvalException(label)
|
||||
|
||||
private[this] final class EvalSourceFile(name: String, startLine: Int, contents: String)
|
||||
extends BatchSourceFile(name, contents) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
* 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 = ()
|
||||
}
|
||||
|
|
@ -19,7 +19,7 @@ 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(_ => reporter, None)
|
||||
private[this] lazy val eval = new Eval(_ => new ForwardingReporter(reporter), None)
|
||||
|
||||
property("inferred integer") = forAll { (i: Int) =>
|
||||
val result = eval.eval(i.toString)
|
||||
|
|
@ -46,7 +46,7 @@ class EvalTest extends Properties("eval") {
|
|||
|
||||
property("backed local class") = forAll { (i: Int) =>
|
||||
IO.withTemporaryDirectory { dir =>
|
||||
val eval = new Eval(_ => reporter, backing = Some(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)) &&
|
||||
|
|
|
|||
|
|
@ -330,6 +330,18 @@ class NetworkClient(
|
|||
|
||||
val cmd = arguments.sbtLaunchJar match {
|
||||
case Some(lj) =>
|
||||
if (log) {
|
||||
val sbtScript = if (Properties.isWin) "sbt.bat" else "sbt"
|
||||
console.appendLog(Level.Warn, s"server is started using sbt-launch jar directly")
|
||||
console.appendLog(
|
||||
Level.Warn,
|
||||
"this is not the recommended way: .sbtopts and .jvmopts files are not loaded and SBT_OPTS is ignored"
|
||||
)
|
||||
console.appendLog(
|
||||
Level.Warn,
|
||||
s"either upgrade $sbtScript to its latest version or make sure it is accessible from $$PATH, and run 'sbt bspConfig'"
|
||||
)
|
||||
}
|
||||
List("java") ++ arguments.sbtArguments ++
|
||||
List("-jar", lj, DashDashDetachStdio, DashDashServer)
|
||||
case _ =>
|
||||
|
|
@ -1050,7 +1062,8 @@ object NetworkClient {
|
|||
private[client] val noStdErr = "--no-stderr"
|
||||
private[client] val sbtBase = "--sbt-base-directory"
|
||||
private[client] def parseArgs(args: Array[String]): Arguments = {
|
||||
var sbtScript = if (Properties.isWin) "sbt.bat" else "sbt"
|
||||
val defaultSbtScript = if (Properties.isWin) "sbt.bat" else "sbt"
|
||||
var sbtScript = Properties.propOrNone("sbt.script")
|
||||
var launchJar: Option[String] = None
|
||||
var bsp = false
|
||||
val commandArgs = new mutable.ArrayBuffer[String]
|
||||
|
|
@ -1072,11 +1085,10 @@ object NetworkClient {
|
|||
sbtScript = a
|
||||
.split("--sbt-script=")
|
||||
.lastOption
|
||||
.map(_.replaceAllLiterally("%20", " "))
|
||||
.getOrElse(sbtScript)
|
||||
.orElse(sbtScript)
|
||||
case "--sbt-script" if i + 1 < sanitized.length =>
|
||||
i += 1
|
||||
sbtScript = sanitized(i).replaceAllLiterally("%20", " ")
|
||||
sbtScript = Some(sanitized(i))
|
||||
case a if a.startsWith("--sbt-launch-jar=") =>
|
||||
launchJar = a
|
||||
.split("--sbt-launch-jar=")
|
||||
|
|
@ -1096,12 +1108,17 @@ object NetworkClient {
|
|||
}
|
||||
val base = new File("").getCanonicalFile
|
||||
if (!sbtArguments.contains("-Dsbt.io.virtual=true")) sbtArguments += "-Dsbt.io.virtual=true"
|
||||
if (!sbtArguments.exists(_.startsWith("-Dsbt.script"))) {
|
||||
sbtScript.foreach { sbtScript =>
|
||||
sbtArguments += s"-Dsbt.script=$sbtScript"
|
||||
}
|
||||
}
|
||||
new Arguments(
|
||||
base,
|
||||
sbtArguments.toSeq,
|
||||
commandArgs.toSeq,
|
||||
completionArguments.toSeq,
|
||||
sbtScript,
|
||||
sbtScript.getOrElse(defaultSbtScript).replaceAllLiterally("%20", " "),
|
||||
bsp,
|
||||
launchJar
|
||||
)
|
||||
|
|
|
|||
|
|
@ -332,13 +332,6 @@ object Defaults extends BuildCommon {
|
|||
turbo :== SysProp.turbo,
|
||||
usePipelining :== SysProp.pipelining,
|
||||
exportPipelining := usePipelining.value,
|
||||
useScalaReplJLine :== false,
|
||||
scalaInstanceTopLoader := {
|
||||
// the JLineLoader contains the SbtInterfaceClassLoader
|
||||
if (!useScalaReplJLine.value)
|
||||
classOf[org.jline.terminal.Terminal].getClassLoader // the JLineLoader
|
||||
else classOf[Compilers].getClassLoader // the SbtInterfaceClassLoader
|
||||
},
|
||||
useSuperShell := { if (insideCI.value) false else ITerminal.console.isSupershellEnabled },
|
||||
superShellThreshold :== SysProp.supershellThreshold,
|
||||
superShellMaxTasks :== SysProp.supershellMaxTasks,
|
||||
|
|
@ -429,7 +422,7 @@ object Defaults extends BuildCommon {
|
|||
LanguageServerProtocol.handler(fileConverter.value),
|
||||
BuildServerProtocol.handler(
|
||||
loadedBuild.value,
|
||||
bspWorkspace.value,
|
||||
bspFullWorkspace.value,
|
||||
sbtVersion.value,
|
||||
semanticdbEnabled.value,
|
||||
semanticdbVersion.value
|
||||
|
|
@ -667,6 +660,23 @@ object Defaults extends BuildCommon {
|
|||
|
||||
// This is included into JvmPlugin.projectSettings
|
||||
def compileBase = inTask(console)(compilersSetting :: Nil) ++ compileBaseGlobal ++ Seq(
|
||||
useScalaReplJLine :== false,
|
||||
scalaInstanceTopLoader := {
|
||||
val topLoader = if (!useScalaReplJLine.value) {
|
||||
// the JLineLoader contains the SbtInterfaceClassLoader
|
||||
classOf[org.jline.terminal.Terminal].getClassLoader
|
||||
} else classOf[Compilers].getClassLoader // the SbtInterfaceClassLoader
|
||||
|
||||
// Scala 2.10 shades jline in the console so we need to make sure that it loads a compatible
|
||||
// jansi version. Because of the shading, console does not work with the thin client for 2.10.x.
|
||||
if (scalaVersion.value.startsWith("2.10.")) new ClassLoader(topLoader) {
|
||||
override protected def loadClass(name: String, resolve: Boolean): Class[_] = {
|
||||
if (name.startsWith("org.fusesource")) throw new ClassNotFoundException(name)
|
||||
super.loadClass(name, resolve)
|
||||
}
|
||||
}
|
||||
else topLoader
|
||||
},
|
||||
scalaInstance := scalaInstanceTask.value,
|
||||
crossVersion := (if (crossPaths.value) CrossVersion.binary else CrossVersion.disabled),
|
||||
pluginCrossBuild / sbtBinaryVersion := binarySbtVersion(
|
||||
|
|
@ -1163,16 +1173,6 @@ object Defaults extends BuildCommon {
|
|||
state: State,
|
||||
topLoader: ClassLoader,
|
||||
): ScalaInstance = {
|
||||
// Scala 2.10 shades jline in the console so we need to make sure that it loads a compatible
|
||||
// jansi version. Because of the shading, console does not work with the thin client for 2.10.x.
|
||||
val jansiExclusionLoader = if (version.startsWith("2.10.")) new ClassLoader(topLoader) {
|
||||
override protected def loadClass(name: String, resolve: Boolean): Class[_] = {
|
||||
if (name.startsWith("org.fusesource")) throw new ClassNotFoundException(name)
|
||||
super.loadClass(name, resolve)
|
||||
}
|
||||
}
|
||||
else topLoader
|
||||
|
||||
val classLoaderCache = state.extendedClassLoaderCache
|
||||
val compilerJars = allCompilerJars.filterNot(libraryJars.contains).distinct.toArray
|
||||
val docJars = allDocJars
|
||||
|
|
@ -1181,7 +1181,7 @@ object Defaults extends BuildCommon {
|
|||
.toArray
|
||||
val allJars = libraryJars ++ compilerJars ++ docJars
|
||||
|
||||
val libraryLoader = classLoaderCache(libraryJars.toList, jansiExclusionLoader)
|
||||
val libraryLoader = classLoaderCache(libraryJars.toList, topLoader)
|
||||
val compilerLoader = classLoaderCache(compilerJars.toList, libraryLoader)
|
||||
val fullLoader =
|
||||
if (docJars.isEmpty) compilerLoader
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import sbt.librarymanagement.{ Resolver, UpdateReport }
|
|||
import sbt.std.Transform.DummyTaskMap
|
||||
import sbt.util.{ Logger, Show }
|
||||
import sbt.BuildSyntax._
|
||||
import sbt.internal.bsp.BuildTargetIdentifier
|
||||
|
||||
import scala.annotation.nowarn
|
||||
import scala.Console.RED
|
||||
|
|
@ -143,14 +144,19 @@ final case class PluginData(
|
|||
definitionClasspath: Seq[Attributed[File]],
|
||||
resolvers: Option[Vector[Resolver]],
|
||||
report: Option[UpdateReport],
|
||||
scalacOptions: Seq[String]
|
||||
scalacOptions: Seq[String],
|
||||
unmanagedSourceDirectories: Seq[File],
|
||||
unmanagedSources: Seq[File],
|
||||
managedSourceDirectories: Seq[File],
|
||||
managedSources: Seq[File],
|
||||
buildTarget: Option[BuildTargetIdentifier]
|
||||
) {
|
||||
val classpath: Seq[Attributed[File]] = definitionClasspath ++ dependencyClasspath
|
||||
}
|
||||
|
||||
object PluginData {
|
||||
private[sbt] def apply(dependencyClasspath: Def.Classpath): PluginData =
|
||||
PluginData(dependencyClasspath, Nil, None, None, Nil)
|
||||
PluginData(dependencyClasspath, Nil, None, None, Nil, Nil, Nil, Nil, Nil, None)
|
||||
}
|
||||
|
||||
object EvaluateTask {
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ package sbt
|
|||
import java.nio.file.{ Path => NioPath }
|
||||
import java.io.File
|
||||
import java.net.URL
|
||||
|
||||
import lmcoursier.definitions.{ CacheLogger, ModuleMatchers, Reconciliation }
|
||||
import lmcoursier.{ CoursierConfiguration, FallbackDependency }
|
||||
import org.apache.ivy.core.module.descriptor.ModuleDescriptor
|
||||
|
|
@ -26,6 +25,7 @@ import sbt.internal.inc.ScalaInstance
|
|||
import sbt.internal.io.WatchState
|
||||
import sbt.internal.librarymanagement.{ CompatibilityWarningOptions, IvySbt }
|
||||
import sbt.internal.remotecache.RemoteCacheArtifact
|
||||
import sbt.internal.server.BuildServerProtocol.BspFullWorkspace
|
||||
import sbt.internal.server.{ BuildServerReporter, ServerHandler }
|
||||
import sbt.internal.util.{ AttributeKey, ProgressState, SourcePosition }
|
||||
import sbt.io._
|
||||
|
|
@ -398,8 +398,10 @@ object Keys {
|
|||
|
||||
val bspConfig = taskKey[Unit]("Create or update the BSP connection files").withRank(DSetting)
|
||||
val bspEnabled = SettingKey[Boolean](BasicKeys.bspEnabled)
|
||||
val bspSbtEnabled = settingKey[Boolean]("Should BSP export meta-targets for the SBT build itself?")
|
||||
val bspTargetIdentifier = settingKey[BuildTargetIdentifier]("Build target identifier of a project and configuration.").withRank(DSetting)
|
||||
val bspWorkspace = settingKey[Map[BuildTargetIdentifier, Scope]]("Mapping of BSP build targets to sbt scopes").withRank(DSetting)
|
||||
private[sbt] val bspFullWorkspace = settingKey[BspFullWorkspace]("Mapping of BSP build targets to sbt scopes and meta-targets for the SBT build itself").withRank(DSetting)
|
||||
val bspInternalDependencyConfigurations = settingKey[Seq[(ProjectRef, Set[ConfigKey])]]("The project configurations that this configuration depends on, possibly transitivly").withRank(DSetting)
|
||||
val bspWorkspaceBuildTargets = taskKey[Seq[BuildTarget]]("List all the BSP build targets").withRank(DTask)
|
||||
val bspBuildTarget = taskKey[BuildTarget]("Description of the BSP build targets").withRank(DTask)
|
||||
|
|
|
|||
|
|
@ -974,9 +974,6 @@ object BuiltinCommands {
|
|||
st => setupGlobalFileTreeRepository(addCacheStoreFactoryFactory(st))
|
||||
)
|
||||
val s4 = s3.put(Keys.useLog4J.key, Project.extract(s3).get(Keys.useLog4J))
|
||||
// This is a workaround for the console task in dotty which uses the classloader cache.
|
||||
// We need to override the top loader in that case so that it gets the forked jline.
|
||||
s4.extendedClassLoaderCache.setParent(Project.extract(s4).get(Keys.scalaInstanceTopLoader))
|
||||
addSuperShellParams(CheckBuildSources.init(LintUnused.lintUnusedFunc(s4)))
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,19 +8,17 @@
|
|||
package sbt
|
||||
package internal
|
||||
|
||||
import java.io.File
|
||||
import java.net.URI
|
||||
|
||||
import sbt.BuildPaths._
|
||||
import sbt.Def.{ ScopeLocal, ScopedKey, Setting, isDummy }
|
||||
import sbt.Keys._
|
||||
import sbt.Project.inScope
|
||||
import sbt.Scope.GlobalScope
|
||||
import sbt.SlashSyntax0._
|
||||
import sbt.compiler.Eval
|
||||
import sbt.compiler.{ Eval, EvalReporter }
|
||||
import sbt.internal.BuildStreams._
|
||||
import sbt.internal.inc.classpath.ClasspathUtil
|
||||
import sbt.internal.inc.{ ScalaInstance, ZincLmUtil, ZincUtil }
|
||||
import sbt.internal.server.BuildServerEvalReporter
|
||||
import sbt.internal.util.Attributed.data
|
||||
import sbt.internal.util.Types.const
|
||||
import sbt.internal.util.{ Attributed, Settings, ~> }
|
||||
|
|
@ -31,6 +29,8 @@ import sbt.nio.Settings
|
|||
import sbt.util.{ Logger, Show }
|
||||
import xsbti.compile.{ ClasspathOptionsUtil, Compilers }
|
||||
|
||||
import java.io.File
|
||||
import java.net.URI
|
||||
import scala.annotation.{ nowarn, tailrec }
|
||||
import scala.collection.mutable
|
||||
import scala.tools.nsc.reporters.ConsoleReporter
|
||||
|
|
@ -426,14 +426,21 @@ private[sbt] object Load {
|
|||
() => eval
|
||||
}
|
||||
|
||||
def mkEval(unit: BuildUnit): Eval =
|
||||
mkEval(unit.definitions, unit.plugins, unit.plugins.pluginData.scalacOptions)
|
||||
|
||||
def mkEval(defs: LoadedDefinitions, plugs: LoadedPlugins, options: Seq[String]): Eval =
|
||||
mkEval(defs.target ++ plugs.classpath, defs.base, options)
|
||||
def mkEval(unit: BuildUnit): Eval = {
|
||||
val defs = unit.definitions
|
||||
mkEval(defs.target ++ unit.plugins.classpath, defs.base, unit.plugins.pluginData.scalacOptions)
|
||||
}
|
||||
|
||||
def mkEval(classpath: Seq[File], base: File, options: Seq[String]): Eval =
|
||||
new Eval(options, classpath, s => new ConsoleReporter(s), Some(evalOutputDirectory(base)))
|
||||
mkEval(classpath, base, options, EvalReporter.console)
|
||||
|
||||
def mkEval(
|
||||
classpath: Seq[File],
|
||||
base: File,
|
||||
options: Seq[String],
|
||||
mkReporter: scala.tools.nsc.Settings => EvalReporter
|
||||
): Eval =
|
||||
new Eval(options, classpath, mkReporter, Some(evalOutputDirectory(base)))
|
||||
|
||||
/**
|
||||
* This will clean up left-over files in the config-classes directory if they are no longer used.
|
||||
|
|
@ -703,7 +710,13 @@ private[sbt] object Load {
|
|||
|
||||
// NOTE - because we create an eval here, we need a clean-eval later for this URI.
|
||||
lazy val eval = timed("Load.loadUnit: mkEval", log) {
|
||||
mkEval(plugs.classpath, defDir, plugs.pluginData.scalacOptions)
|
||||
def mkReporter(settings: scala.tools.nsc.Settings): EvalReporter =
|
||||
plugs.pluginData.buildTarget match {
|
||||
case None => EvalReporter.console(settings)
|
||||
case Some(buildTarget) =>
|
||||
new BuildServerEvalReporter(buildTarget, new ConsoleReporter(settings))
|
||||
}
|
||||
mkEval(plugs.classpath, defDir, plugs.pluginData.scalacOptions, mkReporter)
|
||||
}
|
||||
val initialProjects = defsScala.flatMap(b => projectsFromBuild(b, normBase)) ++ buildLevelExtraProjects
|
||||
|
||||
|
|
@ -1164,12 +1177,22 @@ private[sbt] object Load {
|
|||
val prod = (Configurations.Runtime / exportedProducts).value
|
||||
val cp = (Configurations.Runtime / fullClasspath).value
|
||||
val opts = (Configurations.Compile / scalacOptions).value
|
||||
val unmanagedSrcDirs = (Configurations.Compile / unmanagedSourceDirectories).value
|
||||
val unmanagedSrcs = (Configurations.Compile / unmanagedSources).value
|
||||
val managedSrcDirs = (Configurations.Compile / managedSourceDirectories).value
|
||||
val managedSrcs = (Configurations.Compile / managedSources).value
|
||||
val buildTarget = (Configurations.Compile / bspTargetIdentifier).value
|
||||
PluginData(
|
||||
removeEntries(cp, prod),
|
||||
prod,
|
||||
Some(fullResolvers.value.toVector),
|
||||
Some(update.value),
|
||||
opts
|
||||
opts,
|
||||
unmanagedSrcDirs,
|
||||
unmanagedSrcs,
|
||||
managedSrcDirs,
|
||||
managedSrcs,
|
||||
Some(buildTarget)
|
||||
)
|
||||
},
|
||||
scalacOptions += "-Wconf:cat=unused-nowarn:s",
|
||||
|
|
@ -1225,7 +1248,7 @@ private[sbt] object Load {
|
|||
loadPluginDefinition(
|
||||
dir,
|
||||
config,
|
||||
PluginData(config.globalPluginClasspath, Nil, None, None, Nil)
|
||||
PluginData(config.globalPluginClasspath, Nil, None, None, Nil, Nil, Nil, Nil, Nil, None)
|
||||
)
|
||||
|
||||
def buildPlugins(dir: File, s: State, config: LoadBuildConfiguration): LoadedPlugins =
|
||||
|
|
@ -1417,7 +1440,12 @@ final case class LoadBuildConfiguration(
|
|||
data.internalClasspath,
|
||||
Some(data.resolvers),
|
||||
Some(data.updateReport),
|
||||
Nil
|
||||
Nil,
|
||||
Nil,
|
||||
Nil,
|
||||
Nil,
|
||||
Nil,
|
||||
None
|
||||
)
|
||||
case None => PluginData(globalPluginClasspath)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,93 @@
|
|||
/*
|
||||
* sbt
|
||||
* Copyright 2011 - 2018, Lightbend, Inc.
|
||||
* Copyright 2008 - 2010, Mark Harrah
|
||||
* Licensed under Apache License 2.0 (see LICENSE)
|
||||
*/
|
||||
|
||||
package sbt.internal.server
|
||||
|
||||
import sbt.StandardMain.exchange
|
||||
import sbt.compiler.ForwardingReporter
|
||||
import sbt.internal.bsp
|
||||
import sbt.internal.bsp.{
|
||||
BuildTargetIdentifier,
|
||||
Diagnostic,
|
||||
DiagnosticSeverity,
|
||||
PublishDiagnosticsParams,
|
||||
Range,
|
||||
TextDocumentIdentifier
|
||||
}
|
||||
|
||||
import java.nio.file.{ Files, Path, Paths }
|
||||
import scala.collection.mutable
|
||||
import scala.reflect.internal.Reporter
|
||||
import scala.reflect.internal.util.{ DefinedPosition, Position }
|
||||
import scala.tools.nsc.reporters.FilteringReporter
|
||||
import sbt.internal.bsp.codec.JsonProtocol._
|
||||
|
||||
class BuildServerEvalReporter(buildTarget: BuildTargetIdentifier, delegate: FilteringReporter)
|
||||
extends ForwardingReporter(delegate) {
|
||||
private val problemsByFile = mutable.Map[Path, Vector[Diagnostic]]()
|
||||
|
||||
override def doReport(pos: Position, msg: String, severity: Severity): Unit = {
|
||||
for {
|
||||
filePath <- if (pos.source.file.exists) Some(Paths.get(pos.source.file.path)) else None
|
||||
range <- convertToRange(pos)
|
||||
} {
|
||||
val bspSeverity = convertToBsp(severity)
|
||||
val diagnostic = Diagnostic(range, bspSeverity, None, Option("sbt"), msg)
|
||||
problemsByFile(filePath) = problemsByFile.getOrElse(filePath, Vector()) :+ diagnostic
|
||||
val params = PublishDiagnosticsParams(
|
||||
TextDocumentIdentifier(filePath.toUri),
|
||||
buildTarget,
|
||||
originId = None,
|
||||
Vector(diagnostic),
|
||||
reset = false
|
||||
)
|
||||
exchange.notifyEvent("build/publishDiagnostics", params)
|
||||
}
|
||||
super.doReport(pos, msg, severity)
|
||||
}
|
||||
|
||||
override def finalReport(sourceName: String): Unit = {
|
||||
val filePath = Paths.get(sourceName)
|
||||
if (Files.exists(filePath)) {
|
||||
val diagnostics = problemsByFile.getOrElse(filePath, Vector())
|
||||
val params = PublishDiagnosticsParams(
|
||||
textDocument = TextDocumentIdentifier(filePath.toUri),
|
||||
buildTarget,
|
||||
originId = None,
|
||||
diagnostics,
|
||||
reset = true
|
||||
)
|
||||
exchange.notifyEvent("build/publishDiagnostics", params)
|
||||
}
|
||||
}
|
||||
|
||||
private def convertToBsp(severity: Severity): Option[Long] = {
|
||||
val result = severity match {
|
||||
case Reporter.INFO => DiagnosticSeverity.Information
|
||||
case Reporter.WARNING => DiagnosticSeverity.Warning
|
||||
case Reporter.ERROR => DiagnosticSeverity.Error
|
||||
}
|
||||
Some(result)
|
||||
}
|
||||
|
||||
private def convertToRange(pos: Position): Option[Range] = {
|
||||
pos match {
|
||||
case _: DefinedPosition =>
|
||||
val startLine = pos.source.offsetToLine(pos.start)
|
||||
val startChar = pos.start - pos.source.lineToOffset(startLine)
|
||||
val endLine = pos.source.offsetToLine(pos.end)
|
||||
val endChar = pos.end - pos.source.lineToOffset(endLine)
|
||||
Some(
|
||||
Range(
|
||||
bsp.Position(startLine.toLong, startChar.toLong),
|
||||
bsp.Position(endLine.toLong, endChar.toLong)
|
||||
)
|
||||
)
|
||||
case _ => None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -10,25 +10,31 @@ package internal
|
|||
package server
|
||||
|
||||
import java.net.URI
|
||||
import sbt.BuildPaths.{ configurationSources, projectStandard }
|
||||
import sbt.BuildSyntax._
|
||||
import sbt.Def._
|
||||
import sbt.Keys._
|
||||
import sbt.Project._
|
||||
import sbt.ScopeFilter.Make._
|
||||
import sbt.Scoped.richTaskSeq
|
||||
import sbt.SlashSyntax0._
|
||||
import sbt.StandardMain.exchange
|
||||
import sbt.internal.bsp._
|
||||
import sbt.internal.langserver.ErrorCodes
|
||||
import sbt.internal.protocol.JsonRpcRequestMessage
|
||||
import sbt.internal.util.Attributed
|
||||
import sbt.internal.util.{ Attributed, ErrorHandling }
|
||||
import sbt.internal.util.complete.{ Parser, Parsers }
|
||||
import sbt.librarymanagement.Configuration
|
||||
import sbt.librarymanagement.CrossVersion.binaryScalaVersion
|
||||
import sbt.librarymanagement.{ Configuration, ScalaArtifacts }
|
||||
import sbt.std.TaskExtra
|
||||
import sbt.util.Logger
|
||||
import sjsonnew.shaded.scalajson.ast.unsafe.{ JNull, JValue }
|
||||
import sjsonnew.support.scalajson.unsafe.{ CompactPrinter, Converter, Parser => JsonParser }
|
||||
import xsbti.CompileFailed
|
||||
|
||||
import java.io.File
|
||||
import scala.collection.mutable
|
||||
|
||||
// import scala.annotation.nowarn
|
||||
import scala.util.control.NonFatal
|
||||
import scala.util.{ Failure, Success, Try }
|
||||
|
|
@ -47,35 +53,19 @@ object BuildServerProtocol {
|
|||
)
|
||||
|
||||
private val bspReload = "bspReload"
|
||||
private val bspReloadFailed = "bspReloadFailed"
|
||||
private val bspReloadSucceed = "bspReloadSucceed"
|
||||
|
||||
lazy val commands: Seq[Command] = Seq(
|
||||
Command.single(bspReload) { (state, reqId) =>
|
||||
import sbt.BasicCommandStrings._
|
||||
import sbt.internal.CommandStrings._
|
||||
val result = List(
|
||||
StashOnFailure,
|
||||
s"$OnFailure $bspReloadFailed $reqId",
|
||||
LoadProjectImpl,
|
||||
s"$bspReloadSucceed $reqId",
|
||||
PopOnFailure,
|
||||
FailureWall
|
||||
) ::: state
|
||||
result
|
||||
},
|
||||
Command.single(bspReloadFailed) { (state, reqId) =>
|
||||
exchange.respondError(
|
||||
ErrorCodes.InternalError,
|
||||
"reload failed",
|
||||
Some(reqId),
|
||||
state.source
|
||||
)
|
||||
state
|
||||
},
|
||||
Command.single(bspReloadSucceed) { (state, reqId) =>
|
||||
exchange.respondEvent(JNull, Some(reqId), state.source)
|
||||
state
|
||||
try {
|
||||
val newState = BuiltinCommands.doLoadProject(state, Project.LoadAction.Current)
|
||||
exchange.respondEvent(JNull, Some(reqId), state.source)
|
||||
newState
|
||||
} catch {
|
||||
case NonFatal(e) =>
|
||||
val msg = ErrorHandling.reducedToString(e)
|
||||
exchange.respondError(ErrorCodes.InternalError, msg, Some(reqId), state.source)
|
||||
state.fail
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
|
|
@ -93,36 +83,66 @@ object BuildServerProtocol {
|
|||
}
|
||||
},
|
||||
bspEnabled := true,
|
||||
bspWorkspace := bspWorkspaceSetting.value,
|
||||
bspSbtEnabled := true,
|
||||
bspFullWorkspace := bspFullWorkspaceSetting.value,
|
||||
bspWorkspace := bspFullWorkspace.value.scopes,
|
||||
bspWorkspaceBuildTargets := Def.taskDyn {
|
||||
val workspace = Keys.bspWorkspace.value
|
||||
val workspace = Keys.bspFullWorkspace.value
|
||||
val state = Keys.state.value
|
||||
val allTargets = ScopeFilter.in(workspace.values.toSeq)
|
||||
val allTargets = ScopeFilter.in(workspace.scopes.values.toSeq)
|
||||
val sbtTargets: List[Def.Initialize[Task[BuildTarget]]] = workspace.builds.map {
|
||||
case (buildTargetIdentifier, loadedBuildUnit) =>
|
||||
val buildFor = workspace.buildToScope.getOrElse(buildTargetIdentifier, Nil)
|
||||
sbtBuildTarget(loadedBuildUnit, buildTargetIdentifier, buildFor)
|
||||
}.toList
|
||||
Def.task {
|
||||
val buildTargets = Keys.bspBuildTarget.all(allTargets).value.toVector
|
||||
state.respondEvent(WorkspaceBuildTargetsResult(buildTargets))
|
||||
buildTargets
|
||||
val allBuildTargets = buildTargets ++ sbtTargets.join.value
|
||||
state.respondEvent(WorkspaceBuildTargetsResult(allBuildTargets))
|
||||
allBuildTargets
|
||||
}
|
||||
}.value,
|
||||
// https://github.com/build-server-protocol/build-server-protocol/blob/master/docs/specification.md#build-target-sources-request
|
||||
bspBuildTargetSources := Def.inputTaskDyn {
|
||||
val s = state.value
|
||||
val workspace = bspWorkspace.value
|
||||
val targets = spaceDelimited().parsed.map(uri => BuildTargetIdentifier(URI.create(uri)))
|
||||
val filter = ScopeFilter.in(targets.map(workspace))
|
||||
val workspace = bspFullWorkspace.value.filter(targets)
|
||||
val filter = ScopeFilter.in(workspace.scopes.values.toList)
|
||||
// run the worker task concurrently
|
||||
Def.task {
|
||||
val items = bspBuildTargetSourcesItem.all(filter).value
|
||||
val result = SourcesResult(items.toVector)
|
||||
val buildItems = workspace.builds.toVector.map {
|
||||
case (id, loadedBuildUnit) =>
|
||||
val base = loadedBuildUnit.localBase
|
||||
val sbtFiles = configurationSources(base)
|
||||
val pluginData = loadedBuildUnit.unit.plugins.pluginData
|
||||
val all = Vector.newBuilder[SourceItem]
|
||||
def add(fs: Seq[File], sourceItemKind: Int, generated: Boolean): Unit = {
|
||||
fs.foreach(f => all += (SourceItem(f.toURI, sourceItemKind, generated = generated)))
|
||||
}
|
||||
all += (SourceItem(
|
||||
loadedBuildUnit.unit.plugins.base.toURI,
|
||||
SourceItemKind.Directory,
|
||||
generated = false
|
||||
))
|
||||
add(pluginData.unmanagedSourceDirectories, SourceItemKind.Directory, generated = false)
|
||||
add(pluginData.unmanagedSources, SourceItemKind.File, generated = false)
|
||||
add(pluginData.managedSourceDirectories, SourceItemKind.Directory, generated = true)
|
||||
add(pluginData.managedSources, SourceItemKind.File, generated = true)
|
||||
add(sbtFiles, SourceItemKind.File, generated = false)
|
||||
SourcesItem(id, all.result())
|
||||
}
|
||||
val result = SourcesResult((items ++ buildItems).toVector)
|
||||
s.respondEvent(result)
|
||||
}
|
||||
}.evaluated,
|
||||
bspBuildTargetSources / aggregate := false,
|
||||
bspBuildTargetResources := Def.inputTaskDyn {
|
||||
val s = state.value
|
||||
val workspace = bspWorkspace.value
|
||||
val targets = spaceDelimited().parsed.map(uri => BuildTargetIdentifier(URI.create(uri)))
|
||||
val filter = ScopeFilter.in(targets.map(workspace))
|
||||
val workspace = bspFullWorkspace.value.filter(targets)
|
||||
workspace.warnIfBuildsNonEmpty(Method.Resources, s.log)
|
||||
val filter = ScopeFilter.in(workspace.scopes.values.toList)
|
||||
// run the worker task concurrently
|
||||
Def.task {
|
||||
val items = bspBuildTargetResourcesItem.all(filter).value
|
||||
|
|
@ -133,9 +153,9 @@ object BuildServerProtocol {
|
|||
bspBuildTargetResources / aggregate := false,
|
||||
bspBuildTargetDependencySources := Def.inputTaskDyn {
|
||||
val s = state.value
|
||||
val workspace = bspWorkspace.value
|
||||
val targets = spaceDelimited().parsed.map(uri => BuildTargetIdentifier(URI.create(uri)))
|
||||
val filter = ScopeFilter.in(targets.map(workspace))
|
||||
val workspace = bspFullWorkspace.value.filter(targets)
|
||||
val filter = ScopeFilter.in(workspace.scopes.values.toList)
|
||||
// run the worker task concurrently
|
||||
Def.task {
|
||||
import sbt.internal.bsp.codec.JsonProtocol._
|
||||
|
|
@ -147,9 +167,10 @@ object BuildServerProtocol {
|
|||
bspBuildTargetDependencySources / aggregate := false,
|
||||
bspBuildTargetCompile := Def.inputTaskDyn {
|
||||
val s: State = state.value
|
||||
val workspace = bspWorkspace.value
|
||||
val targets = spaceDelimited().parsed.map(uri => BuildTargetIdentifier(URI.create(uri)))
|
||||
val filter = ScopeFilter.in(targets.map(workspace))
|
||||
val workspace = bspFullWorkspace.value.filter(targets)
|
||||
workspace.warnIfBuildsNonEmpty(Method.Compile, s.log)
|
||||
val filter = ScopeFilter.in(workspace.scopes.values.toList)
|
||||
Def.task {
|
||||
val statusCode = Keys.bspBuildTargetCompileItem.all(filter).value.max
|
||||
s.respondEvent(BspCompileResult(None, statusCode))
|
||||
|
|
@ -160,21 +181,40 @@ object BuildServerProtocol {
|
|||
bspBuildTargetTest / aggregate := false,
|
||||
bspBuildTargetScalacOptions := Def.inputTaskDyn {
|
||||
val s = state.value
|
||||
val workspace = bspWorkspace.value
|
||||
|
||||
val targets = spaceDelimited().parsed.map(uri => BuildTargetIdentifier(URI.create(uri)))
|
||||
val filter = ScopeFilter.in(targets.map(workspace))
|
||||
val workspace = bspFullWorkspace.value.filter(targets)
|
||||
val builds = workspace.builds
|
||||
|
||||
val filter = ScopeFilter.in(workspace.scopes.values.toList)
|
||||
Def.task {
|
||||
val items = bspBuildTargetScalacOptionsItem.all(filter).value
|
||||
val result = ScalacOptionsResult(items.toVector)
|
||||
val appProvider = appConfiguration.value.provider()
|
||||
val sbtJars = appProvider.mainClasspath()
|
||||
val buildItems = builds.map {
|
||||
build =>
|
||||
val plugins: LoadedPlugins = build._2.unit.plugins
|
||||
val scalacOptions = plugins.pluginData.scalacOptions
|
||||
val pluginClassPath = plugins.classpath
|
||||
val classpath = (pluginClassPath ++ sbtJars).map(_.toURI).toVector
|
||||
ScalacOptionsItem(
|
||||
build._1,
|
||||
scalacOptions.toVector,
|
||||
classpath,
|
||||
new File(build._2.localBase, "project/target").toURI
|
||||
)
|
||||
}
|
||||
val result = ScalacOptionsResult((items ++ buildItems).toVector)
|
||||
s.respondEvent(result)
|
||||
}
|
||||
}.evaluated,
|
||||
bspBuildTargetScalacOptions / aggregate := false,
|
||||
bspScalaTestClasses := Def.inputTaskDyn {
|
||||
val s = state.value
|
||||
val workspace = bspWorkspace.value
|
||||
val targets = spaceDelimited().parsed.map(uri => BuildTargetIdentifier(URI.create(uri)))
|
||||
val filter = ScopeFilter.in(targets.map(workspace))
|
||||
val workspace = bspFullWorkspace.value.filter(targets)
|
||||
workspace.warnIfBuildsNonEmpty(Method.ScalaTestClasses, s.log)
|
||||
val filter = ScopeFilter.in(workspace.scopes.values.toList)
|
||||
Def.task {
|
||||
val items = bspScalaTestClassesItem.all(filter).value
|
||||
val result = ScalaTestClassesResult(items.toVector, None)
|
||||
|
|
@ -183,9 +223,10 @@ object BuildServerProtocol {
|
|||
}.evaluated,
|
||||
bspScalaMainClasses := Def.inputTaskDyn {
|
||||
val s = state.value
|
||||
val workspace = bspWorkspace.value
|
||||
val targets = spaceDelimited().parsed.map(uri => BuildTargetIdentifier(URI.create(uri)))
|
||||
val filter = ScopeFilter.in(targets.map(workspace))
|
||||
val workspace = bspFullWorkspace.value.filter(targets)
|
||||
workspace.warnIfBuildsNonEmpty(Method.ScalaMainClasses, s.log)
|
||||
val filter = ScopeFilter.in(workspace.scopes.values.toList)
|
||||
Def.task {
|
||||
val items = bspScalaMainClassesItem.all(filter).value
|
||||
val result = ScalaMainClassesResult(items.toVector, None)
|
||||
|
|
@ -247,10 +288,27 @@ object BuildServerProtocol {
|
|||
}
|
||||
}
|
||||
)
|
||||
private object Method {
|
||||
final val Initialize = "build/initialize"
|
||||
final val BuildTargets = "workspace/buildTargets"
|
||||
final val Reload = "workspace/reload"
|
||||
final val Shutdown = "build/shutdown"
|
||||
final val Sources = "buildTarget/sources"
|
||||
final val Resources = "buildTarget/resources"
|
||||
final val DependencySources = "buildTarget/dependencySources"
|
||||
final val Compile = "buildTarget/compile"
|
||||
final val Test = "buildTarget/test"
|
||||
final val Run = "buildTarget/run"
|
||||
final val ScalacOptions = "buildTarget/scalacOptions"
|
||||
final val ScalaTestClasses = "buildTarget/scalaTestClasses"
|
||||
final val ScalaMainClasses = "buildTarget/scalaMainClasses"
|
||||
final val Exit = "build/exit"
|
||||
}
|
||||
identity(Method) // silence spurious "private object Method in object BuildServerProtocol is never used" warning!
|
||||
|
||||
def handler(
|
||||
loadedBuild: LoadedBuild,
|
||||
workspace: Map[BuildTargetIdentifier, Scope],
|
||||
workspace: BspFullWorkspace,
|
||||
sbtVersion: String,
|
||||
semanticdbEnabled: Boolean,
|
||||
semanticdbVersion: String
|
||||
|
|
@ -264,7 +322,7 @@ object BuildServerProtocol {
|
|||
ServerHandler { callback =>
|
||||
ServerIntent(
|
||||
onRequest = {
|
||||
case r if r.method == "build/initialize" =>
|
||||
case r if r.method == Method.Initialize =>
|
||||
val params = Converter.fromJson[InitializeBuildParams](json(r)).get
|
||||
checkMetalsCompatibility(semanticdbEnabled, semanticdbVersion, params, callback.log)
|
||||
|
||||
|
|
@ -277,42 +335,42 @@ object BuildServerProtocol {
|
|||
)
|
||||
callback.jsonRpcRespond(response, Some(r.id)); ()
|
||||
|
||||
case r if r.method == "workspace/buildTargets" =>
|
||||
case r if r.method == Method.BuildTargets =>
|
||||
val _ = callback.appendExec(Keys.bspWorkspaceBuildTargets.key.toString, Some(r.id))
|
||||
|
||||
case r if r.method == "workspace/reload" =>
|
||||
case r if r.method == Method.Reload =>
|
||||
val _ = callback.appendExec(s"$bspReload ${r.id}", Some(r.id))
|
||||
|
||||
case r if r.method == "build/shutdown" =>
|
||||
case r if r.method == Method.Shutdown =>
|
||||
callback.jsonRpcRespond(JNull, Some(r.id))
|
||||
|
||||
case r if r.method == "buildTarget/sources" =>
|
||||
case r if r.method == Method.Sources =>
|
||||
val param = Converter.fromJson[SourcesParams](json(r)).get
|
||||
val targets = param.targets.map(_.uri).mkString(" ")
|
||||
val command = Keys.bspBuildTargetSources.key
|
||||
val _ = callback.appendExec(s"$command $targets", Some(r.id))
|
||||
|
||||
case r if r.method == "buildTarget/dependencySources" =>
|
||||
case r if r.method == Method.DependencySources =>
|
||||
val param = Converter.fromJson[DependencySourcesParams](json(r)).get
|
||||
val targets = param.targets.map(_.uri).mkString(" ")
|
||||
val command = Keys.bspBuildTargetDependencySources.key
|
||||
val _ = callback.appendExec(s"$command $targets", Some(r.id))
|
||||
|
||||
case r if r.method == "buildTarget/compile" =>
|
||||
case r if r.method == Method.Compile =>
|
||||
val param = Converter.fromJson[CompileParams](json(r)).get
|
||||
val targets = param.targets.map(_.uri).mkString(" ")
|
||||
val command = Keys.bspBuildTargetCompile.key
|
||||
val _ = callback.appendExec(s"$command $targets", Some(r.id))
|
||||
|
||||
case r: JsonRpcRequestMessage if r.method == "buildTarget/test" =>
|
||||
case r: JsonRpcRequestMessage if r.method == Method.Test =>
|
||||
val task = bspBuildTargetTest.key
|
||||
val paramStr = CompactPrinter(json(r))
|
||||
val _ = callback.appendExec(s"$task $paramStr", Some(r.id))
|
||||
|
||||
case r if r.method == "buildTarget/run" =>
|
||||
case r if r.method == Method.Run =>
|
||||
val paramJson = json(r)
|
||||
val param = Converter.fromJson[RunParams](json(r)).get
|
||||
val scope = workspace.getOrElse(
|
||||
val scope = workspace.scopes.getOrElse(
|
||||
param.target,
|
||||
throw LangServerError(
|
||||
ErrorCodes.InvalidParams,
|
||||
|
|
@ -328,25 +386,25 @@ object BuildServerProtocol {
|
|||
Some(r.id)
|
||||
)
|
||||
|
||||
case r if r.method == "buildTarget/scalacOptions" =>
|
||||
case r if r.method == Method.ScalacOptions =>
|
||||
val param = Converter.fromJson[ScalacOptionsParams](json(r)).get
|
||||
val targets = param.targets.map(_.uri).mkString(" ")
|
||||
val command = Keys.bspBuildTargetScalacOptions.key
|
||||
val _ = callback.appendExec(s"$command $targets", Some(r.id))
|
||||
|
||||
case r if r.method == "buildTarget/scalaTestClasses" =>
|
||||
case r if r.method == Method.ScalaTestClasses =>
|
||||
val param = Converter.fromJson[ScalaTestClassesParams](json(r)).get
|
||||
val targets = param.targets.map(_.uri).mkString(" ")
|
||||
val command = Keys.bspScalaTestClasses.key
|
||||
val _ = callback.appendExec(s"$command $targets", Some(r.id))
|
||||
|
||||
case r if r.method == "buildTarget/scalaMainClasses" =>
|
||||
case r if r.method == Method.ScalaMainClasses =>
|
||||
val param = Converter.fromJson[ScalaMainClassesParams](json(r)).get
|
||||
val targets = param.targets.map(_.uri).mkString(" ")
|
||||
val command = Keys.bspScalaMainClasses.key
|
||||
val _ = callback.appendExec(s"$command $targets", Some(r.id))
|
||||
|
||||
case r if r.method == "buildTarget/resources" =>
|
||||
case r if r.method == Method.Resources =>
|
||||
val param = Converter.fromJson[ResourcesParams](json(r)).get
|
||||
val targets = param.targets.map(_.uri).mkString(" ")
|
||||
val command = Keys.bspBuildTargetResources.key
|
||||
|
|
@ -354,7 +412,7 @@ object BuildServerProtocol {
|
|||
},
|
||||
onResponse = PartialFunction.empty,
|
||||
onNotification = {
|
||||
case r if r.method == "build/exit" =>
|
||||
case r if r.method == Method.Exit =>
|
||||
val _ = callback.appendExec(BasicCommandStrings.TerminateAction, None)
|
||||
},
|
||||
)
|
||||
|
|
@ -403,7 +461,7 @@ object BuildServerProtocol {
|
|||
)
|
||||
|
||||
@nowarn
|
||||
private def bspWorkspaceSetting: Def.Initialize[Map[BuildTargetIdentifier, Scope]] =
|
||||
private def bspFullWorkspaceSetting: Def.Initialize[BspFullWorkspace] =
|
||||
Def.settingDyn {
|
||||
val loadedBuild = Keys.loadedBuild.value
|
||||
|
||||
|
|
@ -423,11 +481,32 @@ object BuildServerProtocol {
|
|||
.map(_ / Keys.bspEnabled)
|
||||
.join
|
||||
.value
|
||||
val result = for {
|
||||
val buildsMap =
|
||||
mutable.HashMap[BuildTargetIdentifier, mutable.ListBuffer[BuildTargetIdentifier]]()
|
||||
|
||||
val scopeMap = for {
|
||||
(targetId, scope, bspEnabled) <- (targetIds, scopes, bspEnabled).zipped
|
||||
if bspEnabled
|
||||
} yield targetId -> scope
|
||||
result.toMap
|
||||
} yield {
|
||||
scope.project.toOption match {
|
||||
case Some(ProjectRef(buildUri, _)) =>
|
||||
val loadedBuildUnit = loadedBuild.units(buildUri)
|
||||
buildsMap.getOrElseUpdate(
|
||||
toSbtTargetId(loadedBuildUnit),
|
||||
new mutable.ListBuffer
|
||||
) += targetId
|
||||
}
|
||||
targetId -> scope
|
||||
}
|
||||
val buildMap = if (bspSbtEnabled.value) {
|
||||
for (loadedBuildUnit <- loadedBuild.units.values) yield {
|
||||
val rootProjectId = loadedBuildUnit.root
|
||||
toSbtTargetId(loadedBuildUnit) -> loadedBuildUnit
|
||||
}
|
||||
} else {
|
||||
Nil
|
||||
}
|
||||
BspFullWorkspace(scopeMap.toMap, buildMap.toMap, buildsMap.mapValues(_.result()).toMap)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -469,6 +548,43 @@ object BuildServerProtocol {
|
|||
}
|
||||
}
|
||||
|
||||
private def sbtBuildTarget(
|
||||
loadedUnit: LoadedBuildUnit,
|
||||
buildTargetIdentifier: BuildTargetIdentifier,
|
||||
buildFor: Seq[BuildTargetIdentifier]
|
||||
): Def.Initialize[Task[BuildTarget]] = Def.task {
|
||||
val scalaProvider = appConfiguration.value.provider().scalaProvider()
|
||||
appConfiguration.value.provider().mainClasspath()
|
||||
val scalaJars = scalaProvider.jars()
|
||||
val compileData = ScalaBuildTarget(
|
||||
scalaOrganization = ScalaArtifacts.Organization,
|
||||
scalaVersion = scalaProvider.version(),
|
||||
scalaBinaryVersion = binaryScalaVersion(scalaProvider.version()),
|
||||
platform = ScalaPlatform.JVM,
|
||||
jars = scalaJars.toVector.map(_.toURI.toString)
|
||||
)
|
||||
val sbtVersionValue = sbtVersion.value
|
||||
val sbtData = SbtBuildTarget(
|
||||
sbtVersionValue,
|
||||
loadedUnit.imports.toVector,
|
||||
compileData,
|
||||
None,
|
||||
buildFor.toVector
|
||||
)
|
||||
|
||||
BuildTarget(
|
||||
buildTargetIdentifier,
|
||||
toSbtTargetIdName(loadedUnit),
|
||||
projectStandard(loadedUnit.unit.localBase).toURI,
|
||||
Vector(),
|
||||
BuildTargetCapabilities(canCompile = false, canTest = false, canRun = false),
|
||||
BuildServerConnection.languages,
|
||||
Vector(),
|
||||
"sbt",
|
||||
data = Converter.toJsonUnsafe(sbtData),
|
||||
)
|
||||
}
|
||||
|
||||
private def scalacOptionsTask: Def.Initialize[Task[ScalacOptionsItem]] = Def.taskDyn {
|
||||
val target = Keys.bspTargetIdentifier.value
|
||||
val scalacOptions = Keys.scalacOptions.value
|
||||
|
|
@ -571,7 +687,7 @@ object BuildServerProtocol {
|
|||
.map(_.flatMap(json => Converter.fromJson[TestParams](json)))
|
||||
.parsed
|
||||
.get
|
||||
val workspace = bspWorkspace.value
|
||||
val workspace = bspFullWorkspace.value
|
||||
|
||||
val resultTask: Def.Initialize[Task[Result[Seq[Unit]]]] = testParams.dataKind match {
|
||||
case Some("scala-test") =>
|
||||
|
|
@ -582,7 +698,7 @@ object BuildServerProtocol {
|
|||
case Success(value) => value.testClasses
|
||||
}
|
||||
val testTasks: Seq[Def.Initialize[Task[Unit]]] = items.map { item =>
|
||||
val scope = workspace(item.target)
|
||||
val scope = workspace.scopes(item.target)
|
||||
item.classes.toList match {
|
||||
case Nil => Def.task(())
|
||||
case classes =>
|
||||
|
|
@ -599,7 +715,7 @@ object BuildServerProtocol {
|
|||
|
||||
case None =>
|
||||
// run allTests in testParams.targets
|
||||
val filter = ScopeFilter.in(testParams.targets.map(workspace))
|
||||
val filter = ScopeFilter.in(testParams.targets.map(workspace.scopes))
|
||||
test.all(filter).result
|
||||
}
|
||||
|
||||
|
|
@ -644,7 +760,7 @@ object BuildServerProtocol {
|
|||
|
||||
@nowarn
|
||||
private def internalDependencyConfigurationsSetting = Def.settingDyn {
|
||||
val allScopes = bspWorkspace.value.map { case (_, scope) => scope }.toSet
|
||||
val allScopes = bspFullWorkspace.value.scopes.map { case (_, scope) => scope }.toSet
|
||||
val directDependencies = Keys.internalDependencyConfigurations.value
|
||||
.map {
|
||||
case (project, rawConfigs) =>
|
||||
|
|
@ -705,6 +821,20 @@ object BuildServerProtocol {
|
|||
)
|
||||
}
|
||||
|
||||
// naming convention still seems like the only reliable way to get IntelliJ to import this correctly
|
||||
// https://github.com/JetBrains/intellij-scala/blob/a54c2a7c157236f35957049cbfd8c10587c9e60c/scala/scala-impl/src/org/jetbrains/sbt/language/SbtFileImpl.scala#L82-L84
|
||||
private def toSbtTargetIdName(ref: LoadedBuildUnit): String = {
|
||||
ref.root + "-build"
|
||||
}
|
||||
private def toSbtTargetId(ref: LoadedBuildUnit): BuildTargetIdentifier = {
|
||||
val name = toSbtTargetIdName(ref)
|
||||
val build = ref.unit.uri
|
||||
val sanitized = build.toString.indexOf("#") match {
|
||||
case i if i > 0 => build.toString.take(i)
|
||||
case _ => build.toString
|
||||
}
|
||||
BuildTargetIdentifier(new URI(sanitized + "#" + name))
|
||||
}
|
||||
private def toId(ref: ProjectReference, config: Configuration): BuildTargetIdentifier =
|
||||
ref match {
|
||||
case ProjectRef(build, project) =>
|
||||
|
|
@ -733,4 +863,23 @@ object BuildServerProtocol {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** The regular targets for each scope and meta-targets for the SBT build. */
|
||||
private[sbt] final case class BspFullWorkspace(
|
||||
scopes: Map[BuildTargetIdentifier, Scope],
|
||||
builds: Map[BuildTargetIdentifier, LoadedBuildUnit],
|
||||
buildToScope: Map[BuildTargetIdentifier, Seq[BuildTargetIdentifier]]
|
||||
) {
|
||||
def filter(targets: Seq[BuildTargetIdentifier]): BspFullWorkspace = {
|
||||
val set = targets.toSet
|
||||
def filterMap[T](map: Map[BuildTargetIdentifier, T]) = map.filter(x => set.contains(x._1))
|
||||
BspFullWorkspace(filterMap(scopes), filterMap(builds), buildToScope)
|
||||
}
|
||||
def warnIfBuildsNonEmpty(method: String, log: Logger): Unit = {
|
||||
if (builds.nonEmpty)
|
||||
log.warn(
|
||||
s"$method is a no-op for build.sbt targets: ${builds.keys.mkString("[", ",", "]")}"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -114,7 +114,7 @@ object FakeState {
|
|||
Nil
|
||||
)
|
||||
|
||||
val pluginData = PluginData(Nil, Nil, None, None, Nil)
|
||||
val pluginData = PluginData(Nil, Nil, None, None, Nil, Nil, Nil, Nil, Nil, None)
|
||||
val builds: DetectedModules[BuildDef] = new DetectedModules[BuildDef](Nil)
|
||||
|
||||
val detectedAutoPlugins: Seq[DetectedAutoPlugin] =
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ object Dependencies {
|
|||
private val libraryManagementCore = "org.scala-sbt" %% "librarymanagement-core" % lmVersion
|
||||
private val libraryManagementIvy = "org.scala-sbt" %% "librarymanagement-ivy" % lmVersion
|
||||
|
||||
val launcherVersion = "1.3.2"
|
||||
val launcherVersion = "1.3.3"
|
||||
val launcherInterface = "org.scala-sbt" % "launcher-interface" % launcherVersion
|
||||
val rawLauncher = "org.scala-sbt" % "launcher" % launcherVersion
|
||||
val testInterface = "org.scala-sbt" % "test-interface" % "1.0"
|
||||
|
|
|
|||
|
|
@ -7,12 +7,14 @@
|
|||
|
||||
package sbt.internal.bsp
|
||||
|
||||
import java.io.File
|
||||
|
||||
import sbt.internal.bsp
|
||||
import sbt.internal.bsp.codec.JsonProtocol.BspConnectionDetailsFormat
|
||||
import sbt.io.IO
|
||||
import sjsonnew.support.scalajson.unsafe.{ CompactPrinter, Converter }
|
||||
|
||||
import java.io.File
|
||||
import java.nio.file.{ Files, Paths }
|
||||
import scala.util.Properties
|
||||
|
||||
object BuildServerConnection {
|
||||
final val name = "sbt"
|
||||
final val bspVersion = "2.0.0-M5"
|
||||
|
|
@ -21,14 +23,25 @@ object BuildServerConnection {
|
|||
private final val SbtLaunchJar = "sbt-launch(-.*)?\\.jar".r
|
||||
|
||||
private[sbt] def writeConnectionFile(sbtVersion: String, baseDir: File): Unit = {
|
||||
import bsp.codec.JsonProtocol._
|
||||
val bspConnectionFile = new File(baseDir, ".bsp/sbt.json")
|
||||
val javaHome = System.getProperty("java.home")
|
||||
val classPath = System.getProperty("java.class.path")
|
||||
|
||||
val sbtScript = Option(System.getProperty("sbt.script"))
|
||||
.orElse(sbtScriptInPath)
|
||||
.map(script => s"-Dsbt.script=$script")
|
||||
|
||||
// IntelliJ can start sbt even if the sbt script is not accessible from $PATH.
|
||||
// To do so it uses its own bundled sbt-launch.jar.
|
||||
// In that case, we must pass the path of the sbt-launch.jar to the BSP connection
|
||||
// so that the server can be started.
|
||||
// A known problem in that situation is that the .sbtopts and .jvmopts are not loaded.
|
||||
val sbtLaunchJar = classPath
|
||||
.split(File.pathSeparator)
|
||||
.find(jar => SbtLaunchJar.findFirstIn(jar).nonEmpty)
|
||||
.map(_.replaceAllLiterally(" ", "%20"))
|
||||
.map(jar => s"--sbt-launch-jar=$jar")
|
||||
|
||||
val argv =
|
||||
Vector(
|
||||
s"$javaHome/bin/java",
|
||||
|
|
@ -38,9 +51,21 @@ object BuildServerConnection {
|
|||
classPath,
|
||||
"xsbt.boot.Boot",
|
||||
"-bsp"
|
||||
) ++ sbtLaunchJar.map(jar => s"--sbt-launch-jar=$jar")
|
||||
) ++ sbtScript.orElse(sbtLaunchJar)
|
||||
val details = BspConnectionDetails(name, sbtVersion, bspVersion, languages, argv)
|
||||
val json = Converter.toJson(details).get
|
||||
IO.write(bspConnectionFile, CompactPrinter(json), append = false)
|
||||
}
|
||||
|
||||
private def sbtScriptInPath: Option[String] = {
|
||||
// For those who use an old sbt script, the -Dsbt.script is not set
|
||||
// As a fallback we try to find the sbt script in $PATH
|
||||
val fileName = if (Properties.isWin) "sbt.bat" else "sbt"
|
||||
val envPath = Option(System.getenv("PATH")).getOrElse("")
|
||||
val allPaths = envPath.split(File.pathSeparator).map(Paths.get(_))
|
||||
allPaths
|
||||
.map(_.resolve(fileName))
|
||||
.find(file => Files.exists(file) && Files.isExecutable(file))
|
||||
.map(_.toString.replaceAllLiterally(" ", "%20"))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
11
sbt
11
sbt
|
|
@ -299,6 +299,16 @@ addDefaultMemory() {
|
|||
fi
|
||||
}
|
||||
|
||||
addSbtScriptProperty () {
|
||||
if [[ "${java_args[@]}" == *-Dsbt.script=* ]]; then
|
||||
:
|
||||
else
|
||||
sbt_script=$0
|
||||
sbt_script=${sbt_script/ /%20}
|
||||
addJava "-Dsbt.script=$sbt_script"
|
||||
fi
|
||||
}
|
||||
|
||||
require_arg () {
|
||||
local type="$1"
|
||||
local opt="$2"
|
||||
|
|
@ -769,6 +779,7 @@ else
|
|||
java_version="$(jdk_version)"
|
||||
vlog "[process_args] java_version = '$java_version'"
|
||||
addDefaultMemory
|
||||
addSbtScriptProperty
|
||||
set -- "${residual_args[@]}"
|
||||
argumentCount=$#
|
||||
run
|
||||
|
|
|
|||
|
|
@ -7,10 +7,19 @@
|
|||
|
||||
package testpkg
|
||||
|
||||
import sbt.internal.bsp.SourcesResult
|
||||
import sbt.internal.bsp.WorkspaceBuildTargetsResult
|
||||
import sbt.internal.langserver.ErrorCodes
|
||||
import sbt.IO
|
||||
|
||||
import java.io.File
|
||||
import scala.concurrent.duration._
|
||||
|
||||
// starts svr using server-test/buildserver and perform custom server tests
|
||||
object BuildServerTest extends AbstractServerTest {
|
||||
|
||||
import sbt.internal.bsp.codec.JsonProtocol._
|
||||
|
||||
override val testDirectory: String = "buildserver"
|
||||
|
||||
test("build/initialize") { _ =>
|
||||
|
|
@ -26,33 +35,56 @@ object BuildServerTest extends AbstractServerTest {
|
|||
"""{ "jsonrpc": "2.0", "id": "16", "method": "workspace/buildTargets", "params": {} }"""
|
||||
)
|
||||
assert(processing("workspace/buildTargets"))
|
||||
assert {
|
||||
svr.waitForString(10.seconds) { s =>
|
||||
(s contains """"id":"16"""") &&
|
||||
(s contains """"displayName":"util"""")
|
||||
}
|
||||
}
|
||||
val result = svr.waitFor[WorkspaceBuildTargetsResult](10.seconds)
|
||||
val utilTarget = result.targets.find(_.displayName.contains("util")).get
|
||||
assert(utilTarget.id.uri.toString.endsWith("#util/Compile"))
|
||||
val buildServerBuildTarget =
|
||||
result.targets.find(_.displayName.contains("buildserver-build")).get
|
||||
assert(buildServerBuildTarget.id.uri.toString.endsWith("#buildserver-build"))
|
||||
}
|
||||
|
||||
test("buildTarget/sources") { _ =>
|
||||
val x = s"${svr.baseDirectory.getAbsoluteFile.toURI}#util/Compile"
|
||||
val buildTarget = buildTargetUri("util", "Compile")
|
||||
svr.sendJsonRpc(
|
||||
s"""{ "jsonrpc": "2.0", "id": "24", "method": "buildTarget/sources", "params": {
|
||||
| "targets": [{ "uri": "$x" }]
|
||||
| "targets": [{ "uri": "$buildTarget" }]
|
||||
|} }""".stripMargin
|
||||
)
|
||||
assert(processing("buildTarget/sources"))
|
||||
assert(svr.waitForString(10.seconds) { s =>
|
||||
(s contains """"id":"24"""") &&
|
||||
(s contains "util/src/main/scala")
|
||||
})
|
||||
val s = svr.waitFor[SourcesResult](10.seconds)
|
||||
val sources = s.items.head.sources.map(_.uri)
|
||||
assert(sources.contains(new File(svr.baseDirectory, "util/src/main/scala").toURI))
|
||||
}
|
||||
test("buildTarget/sources SBT") { _ =>
|
||||
val x = s"${svr.baseDirectory.getAbsoluteFile.toURI}#buildserver-build"
|
||||
svr.sendJsonRpc(
|
||||
s"""{ "jsonrpc": "2.0", "id": "25", "method": "buildTarget/sources", "params": {
|
||||
| "targets": [{ "uri": "$x" }]
|
||||
|} }""".stripMargin
|
||||
)
|
||||
assert(processing("buildTarget/sources"))
|
||||
val s = svr.waitFor[SourcesResult](10.seconds)
|
||||
val sources = s.items.head.sources.map(_.uri).sorted
|
||||
val expectedSources = Vector(
|
||||
"build.sbt",
|
||||
"project/",
|
||||
"project/A.scala",
|
||||
"project/src/main/java",
|
||||
"project/src/main/scala-2",
|
||||
"project/src/main/scala-2.12",
|
||||
"project/src/main/scala-sbt-1.0",
|
||||
"project/src/main/scala/",
|
||||
"project/src/main/scala/B.scala",
|
||||
"project/target/scala-2.12/sbt-1.0/src_managed/main"
|
||||
).map(rel => new File(svr.baseDirectory.getAbsoluteFile, rel).toURI).sorted
|
||||
assert(sources == expectedSources)
|
||||
}
|
||||
|
||||
test("buildTarget/compile") { _ =>
|
||||
val x = s"${svr.baseDirectory.getAbsoluteFile.toURI}#util/Compile"
|
||||
val buildTarget = buildTargetUri("util", "Compile")
|
||||
svr.sendJsonRpc(
|
||||
s"""{ "jsonrpc": "2.0", "id": "32", "method": "buildTarget/compile", "params": {
|
||||
| "targets": [{ "uri": "$x" }]
|
||||
| "targets": [{ "uri": "$buildTarget" }]
|
||||
|} }""".stripMargin
|
||||
)
|
||||
assert(processing("buildTarget/compile"))
|
||||
|
|
@ -63,10 +95,10 @@ object BuildServerTest extends AbstractServerTest {
|
|||
}
|
||||
|
||||
test("buildTarget/scalacOptions") { _ =>
|
||||
val x = s"${svr.baseDirectory.getAbsoluteFile.toURI}#util/Compile"
|
||||
val buildTarget = buildTargetUri("util", "Compile")
|
||||
svr.sendJsonRpc(
|
||||
s"""{ "jsonrpc": "2.0", "id": "40", "method": "buildTarget/scalacOptions", "params": {
|
||||
| "targets": [{ "uri": "$x" }]
|
||||
| "targets": [{ "uri": "$buildTarget" }]
|
||||
|} }""".stripMargin
|
||||
)
|
||||
assert(processing("buildTarget/scalacOptions"))
|
||||
|
|
@ -87,11 +119,66 @@ object BuildServerTest extends AbstractServerTest {
|
|||
})
|
||||
}
|
||||
|
||||
test("workspace/reload: send diagnostic and respond with error") { _ =>
|
||||
// write an other-build.sbt file that does not compile
|
||||
val otherBuildFile = new File(svr.baseDirectory, "other-build.sbt")
|
||||
IO.write(
|
||||
otherBuildFile,
|
||||
"""
|
||||
|val someSettings = Seq(
|
||||
| scalacOptions ++= "-deprecation"
|
||||
|)
|
||||
|""".stripMargin
|
||||
)
|
||||
// reload
|
||||
svr.sendJsonRpc(
|
||||
"""{ "jsonrpc": "2.0", "id": "52", "method": "workspace/reload"}"""
|
||||
)
|
||||
assert(
|
||||
svr.waitForString(10.seconds) { s =>
|
||||
s.contains(s""""buildTarget":{"uri":"$metaBuildTarget"}""") &&
|
||||
s.contains(s""""textDocument":{"uri":"${otherBuildFile.toPath.toUri}"}""") &&
|
||||
s.contains(""""severity":1""") &&
|
||||
s.contains(""""reset":true""")
|
||||
}
|
||||
)
|
||||
assert(
|
||||
svr.waitForString(10.seconds) { s =>
|
||||
s.contains(""""id":"52"""") &&
|
||||
s.contains(""""error"""") &&
|
||||
s.contains(s""""code":${ErrorCodes.InternalError}""") &&
|
||||
s.contains("Type error in expression")
|
||||
}
|
||||
)
|
||||
// fix the other-build.sbt file and reload again
|
||||
IO.write(
|
||||
otherBuildFile,
|
||||
"""
|
||||
|val someSettings = Seq(
|
||||
| scalacOptions += "-deprecation"
|
||||
|)
|
||||
|""".stripMargin
|
||||
)
|
||||
svr.sendJsonRpc(
|
||||
"""{ "jsonrpc": "2.0", "id": "52", "method": "workspace/reload"}"""
|
||||
)
|
||||
// assert received an empty diagnostic
|
||||
assert(
|
||||
svr.waitForString(10.seconds) { s =>
|
||||
s.contains(s""""buildTarget":{"uri":"$metaBuildTarget"}""") &&
|
||||
s.contains(s""""textDocument":{"uri":"${otherBuildFile.toPath.toUri}"}""") &&
|
||||
s.contains(""""diagnostics":[]""") &&
|
||||
s.contains(""""reset":true""")
|
||||
}
|
||||
)
|
||||
IO.delete(otherBuildFile)
|
||||
}
|
||||
|
||||
test("buildTarget/scalaMainClasses") { _ =>
|
||||
val x = s"${svr.baseDirectory.getAbsoluteFile.toURI}#runAndTest/Compile"
|
||||
val buildTarget = buildTargetUri("runAndTest", "Compile")
|
||||
svr.sendJsonRpc(
|
||||
s"""{ "jsonrpc": "2.0", "id": "56", "method": "buildTarget/scalaMainClasses", "params": {
|
||||
| "targets": [{ "uri": "$x" }]
|
||||
| "targets": [{ "uri": "$buildTarget" }]
|
||||
|} }""".stripMargin
|
||||
)
|
||||
assert(processing("buildTarget/scalaMainClasses"))
|
||||
|
|
@ -102,10 +189,10 @@ object BuildServerTest extends AbstractServerTest {
|
|||
}
|
||||
|
||||
test("buildTarget/run") { _ =>
|
||||
val x = s"${svr.baseDirectory.getAbsoluteFile.toURI}#runAndTest/Compile"
|
||||
val buildTarget = buildTargetUri("runAndTest", "Compile")
|
||||
svr.sendJsonRpc(
|
||||
s"""{ "jsonrpc": "2.0", "id": "64", "method": "buildTarget/run", "params": {
|
||||
| "target": { "uri": "$x" },
|
||||
| "target": { "uri": "$buildTarget" },
|
||||
| "dataKind": "scala-main-class",
|
||||
| "data": { "class": "main.Main" }
|
||||
|} }""".stripMargin
|
||||
|
|
@ -122,10 +209,10 @@ object BuildServerTest extends AbstractServerTest {
|
|||
}
|
||||
|
||||
test("buildTarget/scalaTestClasses") { _ =>
|
||||
val x = s"${svr.baseDirectory.getAbsoluteFile.toURI}#runAndTest/Test"
|
||||
val buildTarget = buildTargetUri("runAndTest", "Test")
|
||||
svr.sendJsonRpc(
|
||||
s"""{ "jsonrpc": "2.0", "id": "72", "method": "buildTarget/scalaTestClasses", "params": {
|
||||
| "targets": [{ "uri": "$x" }]
|
||||
| "targets": [{ "uri": "$buildTarget" }]
|
||||
|} }""".stripMargin
|
||||
)
|
||||
assert(processing("buildTarget/scalaTestClasses"))
|
||||
|
|
@ -137,10 +224,10 @@ object BuildServerTest extends AbstractServerTest {
|
|||
}
|
||||
|
||||
test("buildTarget/test: run all tests") { _ =>
|
||||
val x = s"${svr.baseDirectory.getAbsoluteFile.toURI}#runAndTest/Test"
|
||||
val buildTarget = buildTargetUri("runAndTest", "Test")
|
||||
svr.sendJsonRpc(
|
||||
s"""{ "jsonrpc": "2.0", "id": "80", "method": "buildTarget/test", "params": {
|
||||
| "targets": [{ "uri": "$x" }]
|
||||
| "targets": [{ "uri": "$buildTarget" }]
|
||||
|} }""".stripMargin
|
||||
)
|
||||
assert(processing("buildTarget/test"))
|
||||
|
|
@ -151,15 +238,15 @@ object BuildServerTest extends AbstractServerTest {
|
|||
}
|
||||
|
||||
test("buildTarget/test: run one test class") { _ =>
|
||||
val x = s"${svr.baseDirectory.getAbsoluteFile.toURI}#runAndTest/Test"
|
||||
val buildTarget = buildTargetUri("runAndTest", "Test")
|
||||
svr.sendJsonRpc(
|
||||
s"""{ "jsonrpc": "2.0", "id": "84", "method": "buildTarget/test", "params": {
|
||||
| "targets": [{ "uri": "$x" }],
|
||||
| "targets": [{ "uri": "$buildTarget" }],
|
||||
| "dataKind": "scala-test",
|
||||
| "data": {
|
||||
| "testClasses": [
|
||||
| {
|
||||
| "target": { "uri": "$x" },
|
||||
| "target": { "uri": "$buildTarget" },
|
||||
| "classes": ["tests.PassingTest"]
|
||||
| }
|
||||
| ]
|
||||
|
|
@ -174,53 +261,53 @@ object BuildServerTest extends AbstractServerTest {
|
|||
}
|
||||
|
||||
test("buildTarget/compile: report error") { _ =>
|
||||
val x = s"${svr.baseDirectory.getAbsoluteFile.toURI}#reportError/Compile"
|
||||
val buildTarget = buildTargetUri("reportError", "Compile")
|
||||
svr.sendJsonRpc(
|
||||
s"""{ "jsonrpc": "2.0", "id": "88", "method": "buildTarget/compile", "params": {
|
||||
| "targets": [{ "uri": "$x" }]
|
||||
| "targets": [{ "uri": "$buildTarget" }]
|
||||
|} }""".stripMargin
|
||||
)
|
||||
assert(svr.waitForString(10.seconds) { s =>
|
||||
(s contains s""""buildTarget":{"uri":"$x"}""") &&
|
||||
(s contains s""""buildTarget":{"uri":"$buildTarget"}""") &&
|
||||
(s contains """"severity":1""") &&
|
||||
(s contains """"reset":true""")
|
||||
})
|
||||
}
|
||||
|
||||
test("buildTarget/compile: report warning") { _ =>
|
||||
val x = s"${svr.baseDirectory.getAbsoluteFile.toURI}#reportWarning/Compile"
|
||||
val buildTarget = buildTargetUri("reportWarning", "Compile")
|
||||
svr.sendJsonRpc(
|
||||
s"""{ "jsonrpc": "2.0", "id": "90", "method": "buildTarget/compile", "params": {
|
||||
| "targets": [{ "uri": "$x" }]
|
||||
| "targets": [{ "uri": "$buildTarget" }]
|
||||
|} }""".stripMargin
|
||||
)
|
||||
assert(svr.waitForString(10.seconds) { s =>
|
||||
(s contains s""""buildTarget":{"uri":"$x"}""") &&
|
||||
(s contains s""""buildTarget":{"uri":"$buildTarget"}""") &&
|
||||
(s contains """"severity":2""") &&
|
||||
(s contains """"reset":true""")
|
||||
})
|
||||
}
|
||||
|
||||
test("buildTarget/compile: respond error") { _ =>
|
||||
val x = s"${svr.baseDirectory.getAbsoluteFile.toURI}#respondError/Compile"
|
||||
val buildTarget = buildTargetUri("respondError", "Compile")
|
||||
svr.sendJsonRpc(
|
||||
s"""{ "jsonrpc": "2.0", "id": "92", "method": "buildTarget/compile", "params": {
|
||||
| "targets": [{ "uri": "$x" }]
|
||||
| "targets": [{ "uri": "$buildTarget" }]
|
||||
|} }""".stripMargin
|
||||
)
|
||||
assert(svr.waitForString(10.seconds) { s =>
|
||||
s.contains(""""id":"92"""") &&
|
||||
s.contains(""""error"""") &&
|
||||
s.contains(""""code":-32603""") &&
|
||||
s.contains(s""""code":${ErrorCodes.InternalError}""") &&
|
||||
s.contains("custom message")
|
||||
})
|
||||
}
|
||||
|
||||
test("buildTarget/resources") { _ =>
|
||||
val x = s"${svr.baseDirectory.getAbsoluteFile.toURI}#util/Compile"
|
||||
val buildTarget = buildTargetUri("util", "Compile")
|
||||
svr.sendJsonRpc(
|
||||
s"""{ "jsonrpc": "2.0", "id": "96", "method": "buildTarget/resources", "params": {
|
||||
| "targets": [{ "uri": "$x" }]
|
||||
| "targets": [{ "uri": "$buildTarget" }]
|
||||
|} }""".stripMargin
|
||||
)
|
||||
assert(processing("buildTarget/resources"))
|
||||
|
|
@ -250,4 +337,10 @@ object BuildServerTest extends AbstractServerTest {
|
|||
msg.contains(s""""message":"Processing $method"""")
|
||||
}
|
||||
}
|
||||
|
||||
private def buildTargetUri(project: String, config: String): String =
|
||||
s"${svr.baseDirectory.getAbsoluteFile.toURI}#$project/$config"
|
||||
|
||||
private def metaBuildTarget: String =
|
||||
s"${svr.baseDirectory.getAbsoluteFile.toURI}project/#buildserver-build/Compile"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,17 +12,18 @@ import java.net.Socket
|
|||
import java.nio.file.{ Files, Path }
|
||||
import java.util.concurrent.{ LinkedBlockingQueue, TimeUnit }
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
|
||||
import verify._
|
||||
import sbt.{ ForkOptions, OutputStrategy, RunFromSourceMain }
|
||||
import sbt.io.IO
|
||||
import sbt.io.syntax._
|
||||
import sbt.protocol.ClientSocket
|
||||
import sjsonnew.JsonReader
|
||||
import sjsonnew.support.scalajson.unsafe.{ Converter, Parser }
|
||||
|
||||
import scala.annotation.tailrec
|
||||
import scala.concurrent._
|
||||
import scala.concurrent.duration._
|
||||
import scala.util.{ Success, Try }
|
||||
import scala.util.{ Failure, Success, Try }
|
||||
|
||||
trait AbstractServerTest extends TestSuite[Unit] {
|
||||
private var temp: File = _
|
||||
|
|
@ -293,6 +294,57 @@ case class TestServer(
|
|||
}
|
||||
impl()
|
||||
}
|
||||
final def waitFor[T: JsonReader](duration: FiniteDuration): T = {
|
||||
val deadline = duration.fromNow
|
||||
var lastEx: Throwable = null
|
||||
@tailrec def impl(): T =
|
||||
lines.poll(deadline.timeLeft.toMillis, TimeUnit.MILLISECONDS) match {
|
||||
case null =>
|
||||
if (lastEx != null) throw lastEx
|
||||
else throw new TimeoutException
|
||||
case s =>
|
||||
Parser
|
||||
.parseFromString(s)
|
||||
.flatMap(
|
||||
jvalue =>
|
||||
Converter.fromJson[T](
|
||||
jvalue.toStandard
|
||||
.asInstanceOf[sjsonnew.shaded.scalajson.ast.JObject]
|
||||
.value("result")
|
||||
.toUnsafe
|
||||
)
|
||||
) match {
|
||||
case Success(value) =>
|
||||
value
|
||||
case Failure(exception) =>
|
||||
if (deadline.isOverdue) {
|
||||
val ex = new TimeoutException()
|
||||
ex.initCause(exception)
|
||||
throw ex
|
||||
} else {
|
||||
lastEx = exception
|
||||
impl()
|
||||
}
|
||||
}
|
||||
}
|
||||
impl()
|
||||
}
|
||||
final def waitForResponse(duration: FiniteDuration, id: Int): String = {
|
||||
val deadline = duration.fromNow
|
||||
@tailrec def impl(): String =
|
||||
lines.poll(deadline.timeLeft.toMillis, TimeUnit.MILLISECONDS) match {
|
||||
case null =>
|
||||
throw new TimeoutException()
|
||||
case s =>
|
||||
val s1 = s
|
||||
val correctId = s1.contains("\"id\":\"" + id + "\"")
|
||||
if (!correctId && !deadline.isOverdue) impl()
|
||||
else if (deadline.isOverdue)
|
||||
throw new TimeoutException()
|
||||
else s
|
||||
}
|
||||
impl()
|
||||
}
|
||||
|
||||
final def neverReceive(duration: FiniteDuration)(f: String => Boolean): Boolean = {
|
||||
val deadline = duration.fromNow
|
||||
|
|
|
|||
Loading…
Reference in New Issue