Fix BuildServerTest

This commit is contained in:
Adrien Piquerez 2024-03-19 13:04:37 +01:00
parent 924150851c
commit 03ca5365f5
11 changed files with 313 additions and 421 deletions

View File

@ -152,7 +152,7 @@ object Command {
def combine(cmds: Seq[Command]): State => Parser[() => State] = { def combine(cmds: Seq[Command]): State => Parser[() => State] = {
val (simple, arbs) = separateCommands(cmds) val (simple, arbs) = separateCommands(cmds)
state => state =>
arbs.map(_ parser state).foldLeft(simpleParser(simple)(state))(_ | _) arbs.map(_.parser(state)).foldLeft(simpleParser(simple)(state))(_ | _)
} }
private[this] def separateCommands( private[this] def separateCommands(
@ -188,7 +188,7 @@ object Command {
else parse(command, state.nonMultiParser)) match { else parse(command, state.nonMultiParser)) match {
case Right(s) => s() // apply command. command side effects happen here case Right(s) => s() // apply command. command side effects happen here
case Left(errMsg) => case Left(errMsg) =>
state.log error errMsg state.log.error(errMsg)
state.fail state.fail
} }
} }

View File

@ -106,9 +106,11 @@ import sbt.internal.inc.{
MixedAnalyzingCompiler, MixedAnalyzingCompiler,
ScalaInstance ScalaInstance
} }
import xsbti.{ CrossValue, HashedVirtualFileRef, VirtualFile, VirtualFileRef } import sbt.internal.io.Retry
import xsbti.{ CompileFailed, CrossValue, HashedVirtualFileRef, VirtualFile, VirtualFileRef }
import xsbti.compile.{ import xsbti.compile.{
AnalysisContents, AnalysisContents,
AnalysisStore,
ClassFileManagerType, ClassFileManagerType,
ClasspathOptionsUtil, ClasspathOptionsUtil,
CompileAnalysis, CompileAnalysis,
@ -2393,11 +2395,10 @@ object Defaults extends BuildCommon {
*/ */
private[sbt] def compileScalaBackendTask: Initialize[Task[CompileResult]] = Def.task { private[sbt] def compileScalaBackendTask: Initialize[Task[CompileResult]] = Def.task {
val setup: Setup = compileIncSetup.value val setup: Setup = compileIncSetup.value
val useBinary: Boolean = enableBinaryCompileAnalysis.value
val _ = compileIncremental.value val _ = compileIncremental.value
val exportP = exportPipelining.value val exportP = exportPipelining.value
// Save analysis midway if pipelining is enabled // Save analysis midway if pipelining is enabled
val store = MixedAnalyzingCompiler.staticCachedStore(setup.cachePath, !useBinary) val store = analysisStore
val contents = store.unsafeGet() val contents = store.unsafeGet()
if (exportP) { if (exportP) {
// this stores the eary analysis (again) in case the subproject contains a macro // this stores the eary analysis (again) in case the subproject contains a macro
@ -2422,9 +2423,7 @@ object Defaults extends BuildCommon {
.debug(s"${name.value}: compileEarly: blocking on earlyOutputPing") .debug(s"${name.value}: compileEarly: blocking on earlyOutputPing")
earlyOutputPing.await.value earlyOutputPing.await.value
}) { }) {
val useBinary: Boolean = enableBinaryCompileAnalysis.value val store = earlyAnalysisStore
val store =
MixedAnalyzingCompiler.staticCachedStore(earlyCompileAnalysisFile.value.toPath, !useBinary)
store.get.toOption match { store.get.toOption match {
case Some(contents) => contents.getAnalysis case Some(contents) => contents.getAnalysis
case _ => Analysis.empty case _ => Analysis.empty
@ -2436,13 +2435,11 @@ object Defaults extends BuildCommon {
def compileTask: Initialize[Task[CompileAnalysis]] = Def.task { def compileTask: Initialize[Task[CompileAnalysis]] = Def.task {
val setup: Setup = compileIncSetup.value val setup: Setup = compileIncSetup.value
val useBinary: Boolean = enableBinaryCompileAnalysis.value val store = analysisStore
val c = fileConverter.value val c = fileConverter.value
// TODO - expose bytecode manipulation phase. // TODO - expose bytecode manipulation phase.
val analysisResult: CompileResult = manipulateBytecode.value val analysisResult: CompileResult = manipulateBytecode.value
if (analysisResult.hasModified) { if (analysisResult.hasModified) {
val store =
MixedAnalyzingCompiler.staticCachedStore(setup.cacheFile.toPath, !useBinary)
val contents = AnalysisContents.create(analysisResult.analysis(), analysisResult.setup()) val contents = AnalysisContents.create(analysisResult.analysis(), analysisResult.setup())
store.set(contents) store.set(contents)
} }
@ -2455,73 +2452,72 @@ object Defaults extends BuildCommon {
analysis analysis
} }
def compileIncrementalTaskSettings = def compileIncrementalTaskSettings = inTask(compileIncremental)(
inTask(compileIncremental)( Seq(
Seq( (TaskZero / compileIncremental) := {
(TaskZero / compileIncremental) := (Def val bspTask = (compile / bspCompileTask).value
.cachedTask { val result = cachedCompileIncrementalTask.result.value
val s = streams.value val reporter = (compile / bspReporter).value
val ci = (compile / compileInputs).value val store = analysisStore
// This is a cacheable version val ci = (compile / compileInputs).value
val ci2 = (compile / compileInputs2).value result match
val ping = (TaskZero / earlyOutputPing).value case Result.Value(res) =>
val reporter = (compile / bspReporter).value val analysis = store.unsafeGet().getAnalysis()
val setup: Setup = (TaskZero / compileIncSetup).value reporter.sendSuccessReport(analysis)
val useBinary: Boolean = enableBinaryCompileAnalysis.value bspTask.notifySuccess(analysis)
val c = fileConverter.value res
val analysisResult: CompileResult = case Result.Inc(cause) =>
BspCompileTask val compileFailed = cause.directCause.collect { case c: CompileFailed => c }
.compute(bspTargetIdentifier.value, thisProjectRef.value, configuration.value) { reporter.sendFailureReport(ci.options.sources)
bspTask => bspTask.notifyFailure(compileFailed)
// TODO - Should readAnalysis + saveAnalysis be scoped by the compile task too? throw cause
compileIncrementalTaskImpl(bspTask, s, ci, ping, reporter) },
} packagedArtifact := {
val analysisOut = c.toVirtualFile(setup.cachePath()) val (hasModified, out) = compileIncremental.value
val store = artifact.value -> out
MixedAnalyzingCompiler.staticCachedStore(setup.cachePath, !useBinary) },
val contents = artifact := artifactSetting.value,
AnalysisContents.create(analysisResult.analysis(), analysisResult.setup()) artifactClassifier := Some("noresources"),
store.set(contents) artifactPath := artifactPathSetting(artifact).value,
Def.declareOutput(analysisOut)
val dir = classDirectory.value
if (dir / "META-INF" / "MANIFEST.MF").exists then
IO.delete(dir / "META-INF" / "MANIFEST.MF")
// inline mappings
val mappings = Path
.allSubpaths(dir)
.filter(_._1.isFile())
.map { case (p, path) =>
val vf = c.toVirtualFile(p.toPath())
(vf: HashedVirtualFileRef) -> path
}
.toSeq
// inlined to avoid caching mappings
val pkgConfig = Pkg.Configuration(
mappings,
artifactPath.value,
packageOptions.value,
)
val out = Pkg(
pkgConfig,
c,
s.log,
Pkg.timeFromConfiguration(pkgConfig)
)
s.log.debug(s"wrote $out")
Def.declareOutput(out)
analysisResult.hasModified() -> (out: HashedVirtualFileRef)
})
.tag(Tags.Compile, Tags.CPU)
.value,
packagedArtifact := {
val (hasModified, out) = compileIncremental.value
artifact.value -> out
},
artifact := artifactSetting.value,
artifactClassifier := Some("noresources"),
artifactPath := artifactPathSetting(artifact).value,
)
) )
)
private val cachedCompileIncrementalTask = Def
.cachedTask {
val s = streams.value
val ci = (compile / compileInputs).value
val bspTask = (compile / bspCompileTask).value
// This is a cacheable version
val ci2 = (compile / compileInputs2).value
val ping = (TaskZero / earlyOutputPing).value
val setup: Setup = (TaskZero / compileIncSetup).value
val store = analysisStore
val c = fileConverter.value
// TODO - Should readAnalysis + saveAnalysis be scoped by the compile task too?
val analysisResult = Retry(compileIncrementalTaskImpl(bspTask, s, ci, ping))
val analysisOut = c.toVirtualFile(setup.cachePath())
val contents = AnalysisContents.create(analysisResult.analysis(), analysisResult.setup())
store.set(contents)
Def.declareOutput(analysisOut)
val dir = classDirectory.value
if (dir / "META-INF" / "MANIFEST.MF").exists then IO.delete(dir / "META-INF" / "MANIFEST.MF")
// inline mappings
val mappings = Path
.allSubpaths(dir)
.filter(_._1.isFile())
.map { case (p, path) =>
val vf = c.toVirtualFile(p.toPath())
(vf: HashedVirtualFileRef) -> path
}
.toSeq
// inlined to avoid caching mappings
val pkgConfig = Pkg.Configuration(mappings, artifactPath.value, packageOptions.value)
val out = Pkg(pkgConfig, c, s.log, Pkg.timeFromConfiguration(pkgConfig))
s.log.debug(s"wrote $out")
Def.declareOutput(out)
analysisResult.hasModified() -> (out: HashedVirtualFileRef)
}
.tag(Tags.Compile, Tags.CPU)
private val incCompiler = ZincUtil.defaultIncrementalCompiler private val incCompiler = ZincUtil.defaultIncrementalCompiler
private[sbt] def compileJavaTask: Initialize[Task[CompileResult]] = Def.task { private[sbt] def compileJavaTask: Initialize[Task[CompileResult]] = Def.task {
@ -2549,8 +2545,7 @@ object Defaults extends BuildCommon {
task: BspCompileTask, task: BspCompileTask,
s: TaskStreams, s: TaskStreams,
ci: Inputs, ci: Inputs,
promise: PromiseWrap[Boolean], promise: PromiseWrap[Boolean]
reporter: BuildServerReporter,
): CompileResult = { ): CompileResult = {
lazy val x = s.text(ExportStream) lazy val x = s.text(ExportStream)
def onArgs(cs: Compilers) = def onArgs(cs: Compilers) =
@ -2565,16 +2560,12 @@ object Defaults extends BuildCommon {
val compilers: Compilers = ci.compilers val compilers: Compilers = ci.compilers
val setup: Setup = ci.setup val setup: Setup = ci.setup
val i = ci.withCompilers(onArgs(compilers)).withSetup(onProgress(setup)) val i = ci.withCompilers(onArgs(compilers)).withSetup(onProgress(setup))
try try incCompiler.compile(i, s.log)
val result = incCompiler.compile(i, s.log)
reporter.sendSuccessReport(result.getAnalysis)
result
catch catch
case e: Throwable => case e: Throwable =>
if !promise.isCompleted then if !promise.isCompleted then
promise.failure(e) promise.failure(e)
ConcurrentRestrictions.cancelAllSentinels() ConcurrentRestrictions.cancelAllSentinels()
reporter.sendFailureReport(ci.options.sources)
throw e throw e
finally x.close() // workaround for #937 finally x.close() // workaround for #937
} }
@ -2591,12 +2582,8 @@ object Defaults extends BuildCommon {
override def definesClass(classpathEntry: VirtualFile): DefinesClass = override def definesClass(classpathEntry: VirtualFile): DefinesClass =
cachedPerEntryDefinesClassLookup(classpathEntry) cachedPerEntryDefinesClassLookup(classpathEntry)
val extra = extraIncOptions.value.map(t2) val extra = extraIncOptions.value.map(t2)
val useBinary: Boolean = enableBinaryCompileAnalysis.value val store = earlyAnalysisStore
val eapath = earlyCompileAnalysisFile.value.toPath val eaOpt = if exportPipelining.value then Some(store) else None
val eaOpt =
if exportPipelining.value then
Some(MixedAnalyzingCompiler.staticCachedStore(eapath, !useBinary))
else None
Setup.of( Setup.of(
lookup, lookup,
(compile / skip).value, (compile / skip).value,
@ -2672,6 +2659,8 @@ object Defaults extends BuildCommon {
javacOptions.value.toVector, javacOptions.value.toVector,
) )
}, },
bspCompileTask :=
BspCompileTask.start(bspTargetIdentifier.value, thisProjectRef.value, configuration.value)
) )
} }
@ -2698,8 +2687,7 @@ object Defaults extends BuildCommon {
def compileAnalysisSettings: Seq[Setting[_]] = Seq( def compileAnalysisSettings: Seq[Setting[_]] = Seq(
previousCompile := { previousCompile := {
val setup = compileIncSetup.value val setup = compileIncSetup.value
val useBinary: Boolean = enableBinaryCompileAnalysis.value val store = analysisStore
val store = MixedAnalyzingCompiler.staticCachedStore(setup.cacheFile.toPath, !useBinary)
val prev = store.get().toOption match { val prev = store.get().toOption match {
case Some(contents) => case Some(contents) =>
val analysis = Option(contents.getAnalysis).toOptional val analysis = Option(contents.getAnalysis).toOptional
@ -2711,6 +2699,18 @@ object Defaults extends BuildCommon {
} }
) )
private inline def analysisStore: AnalysisStore = {
val setup = compileIncSetup.value
val useBinary = enableBinaryCompileAnalysis.value
MixedAnalyzingCompiler.staticCachedStore(setup.cacheFile.toPath, !useBinary)
}
private inline def earlyAnalysisStore: AnalysisStore = {
val earlyAnalysisPath = earlyCompileAnalysisFile.value.toPath
val useBinary = enableBinaryCompileAnalysis.value
MixedAnalyzingCompiler.staticCachedStore(earlyAnalysisPath, !useBinary)
}
def printWarningsTask: Initialize[Task[Unit]] = def printWarningsTask: Initialize[Task[Unit]] =
Def.task { Def.task {
val analysis = compile.value match { case a: Analysis => a } val analysis = compile.value match { case a: Analysis => a }
@ -4828,7 +4828,7 @@ trait BuildExtra extends BuildCommon with DefExtra {
} }
} }
}) })
.value .evaluated
) ++ inTask(scoped)((config / forkOptions) := forkOptionsTask.value) ) ++ inTask(scoped)((config / forkOptions) := forkOptionsTask.value)
} }

View File

@ -26,7 +26,7 @@ import sbt.internal.io.WatchState
import sbt.internal.librarymanagement.{ CompatibilityWarningOptions, IvySbt } import sbt.internal.librarymanagement.{ CompatibilityWarningOptions, IvySbt }
import sbt.internal.remotecache.RemoteCacheArtifact import sbt.internal.remotecache.RemoteCacheArtifact
import sbt.internal.server.BuildServerProtocol.BspFullWorkspace import sbt.internal.server.BuildServerProtocol.BspFullWorkspace
import sbt.internal.server.{ BuildServerReporter, ServerHandler } import sbt.internal.server.{ BspCompileTask, BuildServerReporter, ServerHandler }
import sbt.internal.util.{ AttributeKey, ProgressState, SourcePosition } import sbt.internal.util.{ AttributeKey, ProgressState, SourcePosition }
import sbt.internal.util.StringAttributeKey import sbt.internal.util.StringAttributeKey
import sbt.io._ import sbt.io._
@ -44,6 +44,7 @@ import xsbti.compile.analysis.ReadStamps
import scala.concurrent.duration.{ Duration, FiniteDuration } import scala.concurrent.duration.{ Duration, FiniteDuration }
import scala.xml.{ NodeSeq, Node => XNode } import scala.xml.{ NodeSeq, Node => XNode }
// format: off // format: off
object Keys { object Keys {
@ -438,6 +439,7 @@ object Keys {
val bspBuildTargetOutputPathsItem = taskKey[OutputPathsItem]("").withRank(DTask) val bspBuildTargetOutputPathsItem = taskKey[OutputPathsItem]("").withRank(DTask)
val bspBuildTargetCompile = inputKey[Unit]("").withRank(DTask) val bspBuildTargetCompile = inputKey[Unit]("").withRank(DTask)
val bspBuildTargetCompileItem = taskKey[Int]("").withRank(DTask) val bspBuildTargetCompileItem = taskKey[Int]("").withRank(DTask)
@cacheLevel(include = Array.empty) private[sbt] val bspCompileTask = taskKey[BspCompileTask]("").withRank(DTask)
val bspBuildTargetTest = inputKey[Unit]("Corresponds to buildTarget/test request").withRank(DTask) val bspBuildTargetTest = inputKey[Unit]("Corresponds to buildTarget/test request").withRank(DTask)
val bspBuildTargetRun = inputKey[Unit]("Corresponds to buildTarget/run request").withRank(DTask) val bspBuildTargetRun = inputKey[Unit]("Corresponds to buildTarget/run request").withRank(DTask)
val bspBuildTargetCleanCache = inputKey[Unit]("Corresponds to buildTarget/cleanCache request").withRank(DTask) val bspBuildTargetCleanCache = inputKey[Unit]("Corresponds to buildTarget/cleanCache request").withRank(DTask)

View File

@ -73,8 +73,8 @@ object RemoteCache {
Def._outputDirectory = Some(outDir) Def._outputDirectory = Some(outDir)
val caches = s.get(BasicKeys.cacheStores) val caches = s.get(BasicKeys.cacheStores)
caches match caches match
case Some(xs) => Def._cacheStore = AggregateActionCacheStore(xs) case Some(xs) if xs.nonEmpty => Def._cacheStore = AggregateActionCacheStore(xs)
case None => case _ =>
val tempDiskCache = (s.baseDir / "target" / "bootcache").toPath() val tempDiskCache = (s.baseDir / "target" / "bootcache").toPath()
Def._cacheStore = DiskActionCacheStore(tempDiskCache) Def._cacheStore = DiskActionCacheStore(tempDiskCache)

View File

@ -103,7 +103,7 @@ object Act {
keyMap: Map[String, AttributeKey[_]], keyMap: Map[String, AttributeKey[_]],
data: Settings[Scope] data: Settings[Scope]
): Parser[ParsedKey] = ): Parser[ParsedKey] =
scopedKeyFull(index, current, defaultConfigs, keyMap) flatMap { choices => scopedKeyFull(index, current, defaultConfigs, keyMap).flatMap { choices =>
select(choices, data)(showRelativeKey2(current)) select(choices, data)(showRelativeKey2(current))
} }
@ -355,7 +355,7 @@ object Act {
val normKeys = taskKeys(_.label) val normKeys = taskKeys(_.label)
val valid = allKnown ++ normKeys val valid = allKnown ++ normKeys
val suggested = normKeys.map(_._1).toSet val suggested = normKeys.map(_._1).toSet
val keyP = filterStrings(examples(ID, suggested, "key"), valid.keySet, "key") map valid val keyP = filterStrings(examples(ID, suggested, "key"), valid.keySet, "key").map(valid)
((token( ((token(
value(keyP).map(_ -> slashSeq) value(keyP).map(_ -> slashSeq)
@ -515,7 +515,7 @@ object Act {
} }
} }
action match { action match {
case SingleAction => akp flatMap evaluate case SingleAction => akp.flatMap(evaluate)
case ShowAction | PrintAction | MultiAction => case ShowAction | PrintAction | MultiAction =>
rep1sep(akp, token(Space)) flatMap { pairs => rep1sep(akp, token(Space)) flatMap { pairs =>
val flat: mutable.ListBuffer[(ScopedKey[_], Seq[String])] = mutable.ListBuffer.empty val flat: mutable.ListBuffer[(ScopedKey[_], Seq[String])] = mutable.ListBuffer.empty

View File

@ -9,57 +9,23 @@ package sbt.internal.server
import sbt._ import sbt._
import sbt.internal.bsp._ import sbt.internal.bsp._
import sbt.internal.io.Retry
import sbt.internal.server.BspCompileTask.{ compileReport, exchange }
import sbt.librarymanagement.Configuration import sbt.librarymanagement.Configuration
import sjsonnew.support.scalajson.unsafe.Converter import sjsonnew.support.scalajson.unsafe.Converter
import xsbti.compile.CompileResult import xsbti.compile.CompileAnalysis
import xsbti.{ CompileFailed, Problem, Severity } import xsbti.{ CompileFailed, Problem, Severity }
import scala.util.control.NonFatal
object BspCompileTask { object BspCompileTask {
private lazy val exchange = StandardMain.exchange
def compute(targetId: BuildTargetIdentifier, project: ProjectRef, config: Configuration)( def start(
compile: BspCompileTask => CompileResult
): CompileResult = {
val task = BspCompileTask(targetId, project, config)
try {
task.notifyStart()
val result = Retry(compile(task))
task.notifySuccess(result)
result
} catch {
case NonFatal(cause) =>
val compileFailed = cause match {
case failed: CompileFailed => Some(failed)
case _ => None
}
task.notifyFailure(compileFailed)
throw cause
}
}
private def apply(
targetId: BuildTargetIdentifier, targetId: BuildTargetIdentifier,
project: ProjectRef, project: ProjectRef,
config: Configuration config: Configuration
): BspCompileTask = { ): BspCompileTask = {
val taskId = TaskId(BuildServerTasks.uniqueId, Vector()) val taskId = TaskId(BuildServerTasks.uniqueId, Vector())
val targetName = BuildTargetName.fromScope(project.project, config.name) val targetName = BuildTargetName.fromScope(project.project, config.name)
BspCompileTask(targetId, targetName, taskId, System.currentTimeMillis()) val task = BspCompileTask(targetId, targetName, taskId, System.currentTimeMillis())
} task.notifyStart()
task
private def compileReport(
problems: Seq[Problem],
targetId: BuildTargetIdentifier,
elapsedTimeMillis: Long
): CompileReport = {
val countBySeverity = problems.groupBy(_.severity).view.mapValues(_.size)
val warnings = countBySeverity.getOrElse(Severity.Warn, 0)
val errors = countBySeverity.getOrElse(Severity.Error, 0)
CompileReport(targetId, None, errors, warnings, Some(elapsedTimeMillis.toInt))
} }
} }
@ -75,16 +41,16 @@ case class BspCompileTask private (
val message = s"Compiling $targetName" val message = s"Compiling $targetName"
val data = Converter.toJsonUnsafe(CompileTask(targetId)) val data = Converter.toJsonUnsafe(CompileTask(targetId))
val params = TaskStartParams(id, startTimeMillis, message, "compile-task", data) val params = TaskStartParams(id, startTimeMillis, message, "compile-task", data)
exchange.notifyEvent("build/taskStart", params) StandardMain.exchange.notifyEvent("build/taskStart", params)
} }
private[sbt] def notifySuccess(result: CompileResult): Unit = { private[sbt] def notifySuccess(analysis: CompileAnalysis): Unit = {
import scala.jdk.CollectionConverters.* import scala.jdk.CollectionConverters.*
val endTimeMillis = System.currentTimeMillis() val endTimeMillis = System.currentTimeMillis()
val elapsedTimeMillis = endTimeMillis - startTimeMillis val elapsedTimeMillis = endTimeMillis - startTimeMillis
val sourceInfos = result.analysis().readSourceInfos().getAllSourceInfos.asScala val sourceInfos = analysis.readSourceInfos().getAllSourceInfos.asScala
val problems = sourceInfos.values.flatMap(_.getReportedProblems).toSeq val problems = sourceInfos.values.flatMap(_.getReportedProblems).toSeq
val report = compileReport(problems, targetId, elapsedTimeMillis) val report = compileReport(problems, elapsedTimeMillis)
val params = TaskFinishParams( val params = TaskFinishParams(
id, id,
endTimeMillis, endTimeMillis,
@ -93,7 +59,7 @@ case class BspCompileTask private (
"compile-report", "compile-report",
Converter.toJsonUnsafe(report) Converter.toJsonUnsafe(report)
) )
exchange.notifyEvent("build/taskFinish", params) StandardMain.exchange.notifyEvent("build/taskFinish", params)
} }
private[sbt] def notifyProgress(percentage: Int, total: Int): Unit = { private[sbt] def notifyProgress(percentage: Int, total: Int): Unit = {
@ -110,14 +76,14 @@ case class BspCompileTask private (
Some("compile-progress"), Some("compile-progress"),
Some(data) Some(data)
) )
exchange.notifyEvent("build/taskProgress", params) StandardMain.exchange.notifyEvent("build/taskProgress", params)
} }
private[sbt] def notifyFailure(cause: Option[CompileFailed]): Unit = { private[sbt] def notifyFailure(cause: Option[CompileFailed]): Unit = {
val endTimeMillis = System.currentTimeMillis() val endTimeMillis = System.currentTimeMillis()
val elapsedTimeMillis = endTimeMillis - startTimeMillis val elapsedTimeMillis = endTimeMillis - startTimeMillis
val problems = cause.map(_.problems().toSeq).getOrElse(Seq.empty[Problem]) val problems = cause.map(_.problems().toSeq).getOrElse(Seq.empty[Problem])
val report = compileReport(problems, targetId, elapsedTimeMillis) val report = compileReport(problems, elapsedTimeMillis)
val params = TaskFinishParams( val params = TaskFinishParams(
id, id,
endTimeMillis, endTimeMillis,
@ -126,6 +92,13 @@ case class BspCompileTask private (
"compile-report", "compile-report",
Converter.toJsonUnsafe(report) Converter.toJsonUnsafe(report)
) )
exchange.notifyEvent("build/taskFinish", params) StandardMain.exchange.notifyEvent("build/taskFinish", params)
}
private def compileReport(problems: Seq[Problem], elapsedTimeMillis: Long): CompileReport = {
val countBySeverity = problems.groupBy(_.severity).view.mapValues(_.size)
val warnings = countBySeverity.getOrElse(Severity.Warn, 0)
val errors = countBySeverity.getOrElse(Severity.Error, 0)
CompileReport(targetId, None, errors, warnings, Some(elapsedTimeMillis.toInt))
} }
} }

View File

@ -38,7 +38,6 @@ import java.io.File
import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicBoolean
import scala.collection.mutable import scala.collection.mutable
// import scala.annotation.nowarn
import scala.util.control.NonFatal import scala.util.control.NonFatal
import scala.util.{ Failure, Success } import scala.util.{ Failure, Success }
import scala.annotation.nowarn import scala.annotation.nowarn
@ -62,14 +61,10 @@ object BuildServerProtocol {
private val bspReload = "bspReload" private val bspReload = "bspReload"
private lazy val targetIdentifierParser: Parser[Seq[BuildTargetIdentifier]] = private val targetIdentifierParser: Parser[Seq[BuildTargetIdentifier]] =
Def Def
.spaceDelimited() .spaceDelimited()
.map { xs => .map(xs => xs.map(uri => BuildTargetIdentifier(URI.create(uri))))
xs.map { uri =>
BuildTargetIdentifier(URI.create(uri))
}
}
lazy val commands: Seq[Command] = Seq( lazy val commands: Seq[Command] = Seq(
Command.single(bspReload) { (state, reqId) => Command.single(bspReload) { (state, reqId) =>
@ -103,7 +98,7 @@ object BuildServerProtocol {
bspSbtEnabled := true, bspSbtEnabled := true,
bspFullWorkspace := bspFullWorkspaceSetting.value, bspFullWorkspace := bspFullWorkspaceSetting.value,
bspWorkspace := bspFullWorkspace.value.scopes, bspWorkspace := bspFullWorkspace.value.scopes,
bspWorkspaceBuildTargets := (Def bspWorkspaceBuildTargets := Def
.task { .task {
val workspace = Keys.bspFullWorkspace.value val workspace = Keys.bspFullWorkspace.value
val state = Keys.state.value val state = Keys.state.value
@ -121,186 +116,137 @@ object BuildServerProtocol {
state.respondEvent(WorkspaceBuildTargetsResult(successfulBuildTargets.toVector)) state.respondEvent(WorkspaceBuildTargetsResult(successfulBuildTargets.toVector))
successfulBuildTargets successfulBuildTargets
} }
}) }
.value, .value,
// https://github.com/build-server-protocol/build-server-protocol/blob/master/docs/specification.md#build-target-sources-request // https://github.com/build-server-protocol/build-server-protocol/blob/master/docs/specification.md#build-target-sources-request
bspBuildTargetSources := (Def bspBuildTargetSources := bspInputTask { (workspace, filter) =>
.input((s: State) => targetIdentifierParser) val items = bspBuildTargetSourcesItem.result.all(filter).value
.flatMapTask { targets => val buildItems = workspace.builds.map { case (id, loadedBuildUnit) =>
val s = state.value val base = loadedBuildUnit.localBase
// val targets = spaceDelimited().parsed.map(uri => BuildTargetIdentifier(URI.create(uri))) val sbtFiles = configurationSources(base)
val workspace = bspFullWorkspace.value.filter(targets) val pluginData = loadedBuildUnit.unit.plugins.pluginData
val filter = ScopeFilter.in(workspace.scopes.values.toList) val dirs = pluginData.unmanagedSourceDirectories
// run the worker task concurrently val sourceFiles = getStandaloneSourceFiles(pluginData.unmanagedSources, dirs)
Def.task { val managedDirs = pluginData.managedSourceDirectories
val items = bspBuildTargetSourcesItem.result.all(filter).value val managedSourceFiles =
val buildItems = workspace.builds.map { case (id, loadedBuildUnit) => getStandaloneSourceFiles(pluginData.managedSources, managedDirs)
val base = loadedBuildUnit.localBase val items =
val sbtFiles = configurationSources(base) dirs.map(toSourceItem(SourceItemKind.Directory, generated = false)) ++
val pluginData = loadedBuildUnit.unit.plugins.pluginData sourceFiles.map(toSourceItem(SourceItemKind.File, generated = false)) ++
val dirs = pluginData.unmanagedSourceDirectories managedDirs.map(toSourceItem(SourceItemKind.Directory, generated = true)) ++
val sourceFiles = getStandaloneSourceFiles(pluginData.unmanagedSources, dirs) managedSourceFiles.map(toSourceItem(SourceItemKind.File, generated = true)) ++
val managedDirs = pluginData.managedSourceDirectories sbtFiles.map(toSourceItem(SourceItemKind.File, generated = false))
val managedSourceFiles = Result.Value(SourcesItem(id, items.toVector))
getStandaloneSourceFiles(pluginData.managedSources, managedDirs) }
val items = val successfulItems = anyOrThrow(items ++ buildItems)
dirs.map(toSourceItem(SourceItemKind.Directory, generated = false)) ++ val result = SourcesResult(successfulItems.toVector)
sourceFiles.map(toSourceItem(SourceItemKind.File, generated = false)) ++ state.value.respondEvent(result)
managedDirs.map(toSourceItem(SourceItemKind.Directory, generated = true)) ++ }.evaluated,
managedSourceFiles.map(toSourceItem(SourceItemKind.File, generated = true)) ++
sbtFiles.map(toSourceItem(SourceItemKind.File, generated = false))
Result.Value(SourcesItem(id, items.toVector))
}
val successfulItems = anyOrThrow(items ++ buildItems)
val result = SourcesResult(successfulItems.toVector)
s.respondEvent(result)
}
})
.value,
bspBuildTargetSources / aggregate := false, bspBuildTargetSources / aggregate := false,
bspBuildTargetResources := (Def bspBuildTargetResources := bspInputTask { (_, filter) =>
.input((s: State) => targetIdentifierParser) val items = bspBuildTargetResourcesItem.result.all(filter).value
.flatMapTask { targets => val successfulItems = anyOrThrow(items)
val s = state.value val result = ResourcesResult(successfulItems.toVector)
val workspace = bspFullWorkspace.value.filter(targets) state.value.respondEvent(result)
workspace.warnIfBuildsNonEmpty(Method.Resources, s.log) }.evaluated,
val filter = ScopeFilter.in(workspace.scopes.values.toList)
// run the worker task concurrently
Def.task {
val items = bspBuildTargetResourcesItem.result.all(filter).value
val successfulItems = anyOrThrow(items)
val result = ResourcesResult(successfulItems.toVector)
s.respondEvent(result)
}
})
.value,
bspBuildTargetResources / aggregate := false, bspBuildTargetResources / aggregate := false,
bspBuildTargetDependencySources := (Def bspBuildTargetDependencySources := bspInputTask { (_, filter) =>
.input((s: State) => targetIdentifierParser) val items = bspBuildTargetDependencySourcesItem.result.all(filter).value
.flatMapTask { targets => val successfulItems = anyOrThrow(items)
val s = state.value val result = DependencySourcesResult(successfulItems.toVector)
val workspace = bspFullWorkspace.value.filter(targets) state.value.respondEvent(result)
val filter = ScopeFilter.in(workspace.scopes.values.toList) }.evaluated,
// run the worker task concurrently
Def.task {
import sbt.internal.bsp.codec.JsonProtocol._
val items = bspBuildTargetDependencySourcesItem.result.all(filter).value
val successfulItems = anyOrThrow(items)
val result = DependencySourcesResult(successfulItems.toVector)
s.respondEvent(result)
}
})
.value,
bspBuildTargetDependencySources / aggregate := false, bspBuildTargetDependencySources / aggregate := false,
bspBuildTargetCompile := (Def bspBuildTargetCompile := bspInputTask { (workspace, filter) =>
.input((s: State) => targetIdentifierParser) val s = state.value
.flatMapTask { targets => workspace.warnIfBuildsNonEmpty(Method.Compile, s.log)
val s: State = state.value val statusCodes = Keys.bspBuildTargetCompileItem.result.all(filter).value
val workspace = bspFullWorkspace.value.filter(targets) val aggregatedStatusCode = allOrThrow(statusCodes) match {
workspace.warnIfBuildsNonEmpty(Method.Compile, s.log) case Seq() => StatusCode.Success
val filter = ScopeFilter.in(workspace.scopes.values.toList) case codes => codes.max
Def.task { }
val statusCodes = Keys.bspBuildTargetCompileItem.result.all(filter).value s.respondEvent(BspCompileResult(None, aggregatedStatusCode))
val aggregatedStatusCode = allOrThrow(statusCodes) match { }.evaluated,
case Seq() => StatusCode.Success bspBuildTargetOutputPaths := bspInputTask { (_, filter) =>
case codes => codes.max val items = bspBuildTargetOutputPathsItem.result.all(filter).value
} val successfulItems = anyOrThrow(items)
s.respondEvent(BspCompileResult(None, aggregatedStatusCode)) val result = OutputPathsResult(successfulItems.toVector)
} state.value.respondEvent(result)
}) }.evaluated,
.value, bspBuildTargetOutputPaths / aggregate := false,
bspBuildTargetCompile / aggregate := false, bspBuildTargetCompile / aggregate := false,
bspBuildTargetTest := bspTestTask.evaluated, bspBuildTargetTest := bspTestTask.evaluated,
bspBuildTargetTest / aggregate := false, bspBuildTargetTest / aggregate := false,
bspBuildTargetCleanCache := (Def bspBuildTargetCleanCache := bspInputTask { (workspace, filter) =>
.input((s: State) => targetIdentifierParser) val s = state.value
.flatMapTask { targets => workspace.warnIfBuildsNonEmpty(Method.CleanCache, s.log)
val s: State = state.value val results = Keys.clean.result.all(filter).value
val workspace = bspFullWorkspace.value.filter(targets) val successes = anyOrThrow(results).size
workspace.warnIfBuildsNonEmpty(Method.CleanCache, s.log)
val filter = ScopeFilter.in(workspace.scopes.values.toList)
Def.task {
val results = Keys.clean.result.all(filter).value
val successes = anyOrThrow(results).size
// When asking to Rebuild Project, IntelliJ sends the root build as an additional target, however it is // When asking to rebuild Project, IntelliJ sends the root build as an additional target,
// not returned as part of the results. In this case, there's 1 build entry in the workspace, and we're // however it is not returned as part of the results. We're checking that the number of
// checking that the executed results plus this entry is equal to the total number of targets. // results equals the number of scopes (not the root build).
// When rebuilding a single module, the root build isn't sent, just the requested targets. // When rebuilding a single module, the root build isn't sent, just the requested targets.
val cleaned = successes + workspace.builds.size == targets.size val cleaned = successes == workspace.scopes.size
s.respondEvent(CleanCacheResult(None, cleaned)) s.respondEvent(CleanCacheResult(None, cleaned))
} }.evaluated,
})
.value,
bspBuildTargetCleanCache / aggregate := false, bspBuildTargetCleanCache / aggregate := false,
bspBuildTargetScalacOptions := (Def bspBuildTargetScalacOptions := bspInputTask { (workspace, filter) =>
.input((s: State) => targetIdentifierParser) val items = bspBuildTargetScalacOptionsItem.result.all(filter).value
.flatMapTask { targets => val appProvider = appConfiguration.value.provider()
val s = state.value val sbtJars = appProvider.mainClasspath()
val workspace = bspFullWorkspace.value.filter(targets) val buildItems = workspace.builds.map { build =>
val builds = workspace.builds val plugins: LoadedPlugins = build._2.unit.plugins
val scalacOptions = plugins.pluginData.scalacOptions
val filter = ScopeFilter.in(workspace.scopes.values.toList) val pluginClasspath = plugins.classpath
Def.task { val converter = plugins.pluginData.converter
val items = bspBuildTargetScalacOptionsItem.result.all(filter).value val classpath =
val appProvider = appConfiguration.value.provider() pluginClasspath.map(converter.toPath).map(_.toFile).map(_.toURI).toVector ++
val sbtJars = appProvider.mainClasspath() (sbtJars).map(_.toURI).toVector
val buildItems = builds.map { build => val item = ScalacOptionsItem(
val plugins: LoadedPlugins = build._2.unit.plugins build._1,
val scalacOptions = plugins.pluginData.scalacOptions scalacOptions.toVector,
val pluginClasspath = plugins.classpath classpath,
val converter = plugins.pluginData.converter new File(build._2.localBase, "project/target").toURI
val classpath = )
pluginClasspath.map(converter.toPath).map(_.toFile).map(_.toURI).toVector ++ Result.Value(item)
(sbtJars).map(_.toURI).toVector }
val item = ScalacOptionsItem( val successfulItems = anyOrThrow(items ++ buildItems)
build._1, val result = ScalacOptionsResult(successfulItems.toVector)
scalacOptions.toVector, state.value.respondEvent(result)
classpath, }.evaluated,
new File(build._2.localBase, "project/target").toURI
)
Result.Value(item)
}
val successfulItems = anyOrThrow(items ++ buildItems)
val result = ScalacOptionsResult(successfulItems.toVector)
s.respondEvent(result)
}
})
.value,
bspBuildTargetScalacOptions / aggregate := false, bspBuildTargetScalacOptions / aggregate := false,
bspScalaTestClasses := (Def bspBuildTargetJVMRunEnvironment := bspInputTask { (_, filter) =>
.input((s: State) => targetIdentifierParser) val items = bspBuildTargetJvmEnvironmentItem.result.all(filter).value
.flatMapTask { targets => val successfulItems = anyOrThrow(items)
val s = state.value val result = JvmRunEnvironmentResult(successfulItems.toVector, None)
val workspace = bspFullWorkspace.value.filter(targets) state.value.respondEvent(result)
workspace.warnIfBuildsNonEmpty(Method.ScalaTestClasses, s.log) }.evaluated,
val filter = ScopeFilter.in(workspace.scopes.values.toList) bspBuildTargetJVMRunEnvironment / aggregate := false,
Def.task { bspBuildTargetJVMTestEnvironment := bspInputTask { (_, filter) =>
val items = bspScalaTestClassesItem.result.all(filter).value val items = bspBuildTargetJvmEnvironmentItem.result.all(filter).value
val successfulItems = anyOrThrow[Seq[ScalaTestClassesItem]](items).flatten val successfulItems = anyOrThrow(items)
val result = ScalaTestClassesResult( val result = JvmTestEnvironmentResult(successfulItems.toVector, None)
items = successfulItems.toVector, state.value.respondEvent(result)
originId = None: Option[String] }.evaluated,
) bspBuildTargetJVMTestEnvironment / aggregate := false,
s.respondEvent(result) bspScalaTestClasses := bspInputTask { (workspace, filter) =>
} val s = state.value
}) val items = bspScalaTestClassesItem.result.all(filter).value
.value, workspace.warnIfBuildsNonEmpty(Method.ScalaTestClasses, s.log)
bspScalaMainClasses := (Def val successfulItems = anyOrThrow[Seq[ScalaTestClassesItem]](items).flatten
.input((s: State) => targetIdentifierParser) val result = ScalaTestClassesResult(
.flatMapTask { targets => items = successfulItems.toVector,
val s = state.value originId = None: Option[String]
val workspace = bspFullWorkspace.value.filter(targets) )
workspace.warnIfBuildsNonEmpty(Method.ScalaMainClasses, s.log) s.respondEvent(result)
val filter = ScopeFilter.in(workspace.scopes.values.toList) }.evaluated,
Def.task { bspScalaMainClasses := bspInputTask { (_, filter) =>
val items = bspScalaMainClassesItem.result.all(filter).value val items = bspScalaMainClassesItem.result.all(filter).value
val successfulItems = anyOrThrow(items) val successfulItems = anyOrThrow(items)
val result = ScalaMainClassesResult(successfulItems.toVector, None) val result = ScalaMainClassesResult(successfulItems.toVector, None)
s.respondEvent(result) state.value.respondEvent(result)
} }.evaluated,
})
.value,
bspScalaMainClasses / aggregate := false bspScalaMainClasses / aggregate := false
) )
@ -344,22 +290,6 @@ object BuildServerProtocol {
bspBuildTargetCompileItem := bspCompileTask.value, bspBuildTargetCompileItem := bspCompileTask.value,
bspBuildTargetRun := bspRunTask.evaluated, bspBuildTargetRun := bspRunTask.evaluated,
bspBuildTargetScalacOptionsItem := scalacOptionsTask.value, bspBuildTargetScalacOptionsItem := scalacOptionsTask.value,
bspBuildTargetJVMRunEnvironment := bspInputTask { (state, _, _, filter) =>
Def.task {
val items = bspBuildTargetJvmEnvironmentItem.result.all(filter).value
val successfulItems = anyOrThrow(items)
val result = JvmRunEnvironmentResult(successfulItems.toVector, None)
state.respondEvent(result)
}
}.evaluated,
bspBuildTargetJVMTestEnvironment := bspInputTask { (state, _, _, filter) =>
Def.task {
val items = bspBuildTargetJvmEnvironmentItem.result.all(filter).value
val successfulItems = anyOrThrow(items)
val result = JvmTestEnvironmentResult(successfulItems.toVector, None)
state.respondEvent(result)
}
}.evaluated,
bspBuildTargetJvmEnvironmentItem := jvmEnvironmentItem().value, bspBuildTargetJvmEnvironmentItem := jvmEnvironmentItem().value,
bspInternalDependencyConfigurations := internalDependencyConfigurationsSetting.value, bspInternalDependencyConfigurations := internalDependencyConfigurationsSetting.value,
bspScalaTestClassesItem := scalaTestClassesTask.value, bspScalaTestClassesItem := scalaTestClassesTask.value,
@ -755,21 +685,16 @@ object BuildServerProtocol {
) )
} }
private def bspInputTask[T]( private inline def bspInputTask[T](
taskImpl: ( inline taskImpl: (BspFullWorkspace, ScopeFilter) => T
State,
Seq[BuildTargetIdentifier],
BspFullWorkspace,
ScopeFilter
) => Def.Initialize[Task[T]]
): Def.Initialize[InputTask[T]] = ): Def.Initialize[InputTask[T]] =
Def Def
.input((s: State) => targetIdentifierParser) .input(_ => targetIdentifierParser)
.flatMapTask { targets => .flatMapTask { targets =>
val s = state.value val s = state.value
val workspace: BspFullWorkspace = bspFullWorkspace.value.filter(targets) val workspace: BspFullWorkspace = bspFullWorkspace.value.filter(targets)
val filter = ScopeFilter.in(workspace.scopes.values.toList) val filter = ScopeFilter.in(workspace.scopes.values.toList)
taskImpl(s, targets, workspace, filter) Def.task(taskImpl(workspace, filter))
} }
private def jvmEnvironmentItem(): Initialize[Task[JvmEnvironmentItem]] = Def.task { private def jvmEnvironmentItem(): Initialize[Task[JvmEnvironmentItem]] = Def.task {

View File

@ -39,9 +39,7 @@ sealed trait BuildServerReporter extends Reporter {
protected def publishDiagnostic(problem: Problem): Unit protected def publishDiagnostic(problem: Problem): Unit
def sendSuccessReport( def sendSuccessReport(analysis: CompileAnalysis): Unit
analysis: CompileAnalysis,
): Unit
def sendFailureReport(sources: Array[VirtualFile]): Unit def sendFailureReport(sources: Array[VirtualFile]): Unit

View File

@ -1,6 +1,7 @@
ThisBuild / scalaVersion := "2.13.8" ThisBuild / scalaVersion := "2.13.8"
Global / serverLog / logLevel := Level.Debug Global / serverLog / logLevel := Level.Debug
Global / cacheStores := Seq.empty
lazy val runAndTest = project.in(file("run-and-test")) lazy val runAndTest = project.in(file("run-and-test"))
.settings( .settings(

View File

@ -7,27 +7,27 @@
package testpkg package testpkg
import sbt.internal.bsp._ import sbt.internal.bsp.*
import sbt.internal.langserver.ErrorCodes import sbt.internal.langserver.ErrorCodes
import sbt.IO import sbt.IO
import sbt.internal.protocol.JsonRpcRequestMessage import sbt.internal.protocol.JsonRpcRequestMessage
import sbt.internal.protocol.codec.JsonRPCProtocol._ import sbt.internal.protocol.codec.JsonRPCProtocol.*
import sjsonnew.JsonWriter import sjsonnew.JsonWriter
import sjsonnew.support.scalajson.unsafe.{ CompactPrinter, Converter } import sjsonnew.support.scalajson.unsafe.{ CompactPrinter, Converter }
import java.io.File import java.io.File
import java.net.URI import java.net.URI
import java.nio.file.Paths import java.nio.file.Files
import scala.concurrent.duration._ import scala.concurrent.duration.*
// starts svr using server-test/buildserver and perform custom server tests // starts svr using server-test/buildserver and perform custom server tests
object BuildServerTest extends AbstractServerTest { class BuildServerTest extends AbstractServerTest {
import sbt.internal.bsp.codec.JsonProtocol._ import sbt.internal.bsp.codec.JsonProtocol._
override val testDirectory: String = "buildserver" override val testDirectory: String = "buildserver"
test("build/initialize") { _ => test("build/initialize") {
initializeRequest() initializeRequest()
assert(svr.waitForString(10.seconds) { s => assert(svr.waitForString(10.seconds) { s =>
(s contains """"id":"8"""") && (s contains """"id":"8"""") &&
@ -36,7 +36,7 @@ object BuildServerTest extends AbstractServerTest {
}) })
} }
test("workspace/buildTargets") { _ => test("workspace/buildTargets") {
svr.sendJsonRpc( svr.sendJsonRpc(
"""{ "jsonrpc": "2.0", "id": "16", "method": "workspace/buildTargets", "params": {} }""" """{ "jsonrpc": "2.0", "id": "16", "method": "workspace/buildTargets", "params": {} }"""
) )
@ -50,7 +50,7 @@ object BuildServerTest extends AbstractServerTest {
assert(!result.targets.exists(_.displayName.contains("badBuildTarget"))) assert(!result.targets.exists(_.displayName.contains("badBuildTarget")))
} }
test("buildTarget/sources") { _ => test("buildTarget/sources") {
val buildTarget = buildTargetUri("util", "Compile") val buildTarget = buildTargetUri("util", "Compile")
val badBuildTarget = buildTargetUri("badBuildTarget", "Compile") val badBuildTarget = buildTargetUri("badBuildTarget", "Compile")
svr.sendJsonRpc(buildTargetSources(24, Seq(buildTarget, badBuildTarget))) svr.sendJsonRpc(buildTargetSources(24, Seq(buildTarget, badBuildTarget)))
@ -59,7 +59,7 @@ object BuildServerTest extends AbstractServerTest {
val sources = s.items.head.sources.map(_.uri) val sources = s.items.head.sources.map(_.uri)
assert(sources.contains(new File(svr.baseDirectory, "util/src/main/scala").toURI)) assert(sources.contains(new File(svr.baseDirectory, "util/src/main/scala").toURI))
} }
test("buildTarget/sources: base sources") { _ => test("buildTarget/sources: base sources") {
val buildTarget = buildTargetUri("buildserver", "Compile") val buildTarget = buildTargetUri("buildserver", "Compile")
svr.sendJsonRpc(buildTargetSources(25, Seq(buildTarget))) svr.sendJsonRpc(buildTargetSources(25, Seq(buildTarget)))
assert(processing("buildTarget/sources")) assert(processing("buildTarget/sources"))
@ -73,7 +73,7 @@ object BuildServerTest extends AbstractServerTest {
assert(sources.contains(expectedSource)) assert(sources.contains(expectedSource))
} }
test("buildTarget/sources: sbt") { _ => test("buildTarget/sources: sbt") {
val x = new URI(s"${svr.baseDirectory.getAbsoluteFile.toURI}#buildserver-build") val x = new URI(s"${svr.baseDirectory.getAbsoluteFile.toURI}#buildserver-build")
svr.sendJsonRpc(buildTargetSources(26, Seq(x))) svr.sendJsonRpc(buildTargetSources(26, Seq(x)))
assert(processing("buildTarget/sources")) assert(processing("buildTarget/sources"))
@ -83,16 +83,15 @@ object BuildServerTest extends AbstractServerTest {
"build.sbt", "build.sbt",
"project/A.scala", "project/A.scala",
"project/src/main/java", "project/src/main/java",
"project/src/main/scala-2", "project/src/main/scala-3",
"project/src/main/scala-2.12", s"project/src/main/scala-sbt-${sbtVersion}",
"project/src/main/scala-sbt-1.0",
"project/src/main/scala/", "project/src/main/scala/",
"project/target/scala-2.12/sbt-1.0/src_managed/main" "target/out/jvm/scala-3.3.1/buildserver-build/src_managed/main"
).map(rel => new File(svr.baseDirectory.getAbsoluteFile, rel).toURI).sorted ).map(rel => new File(svr.baseDirectory.getAbsoluteFile, rel).toURI).sorted
assert(sources == expectedSources) assert(sources == expectedSources)
} }
test("buildTarget/compile") { _ => test("buildTarget/compile") {
val buildTarget = buildTargetUri("util", "Compile") val buildTarget = buildTargetUri("util", "Compile")
compile(buildTarget, id = 32) compile(buildTarget, id = 32)
@ -102,33 +101,31 @@ object BuildServerTest extends AbstractServerTest {
assert(res.statusCode == StatusCode.Success) assert(res.statusCode == StatusCode.Success)
} }
test("buildTarget/compile - reports compilation progress") { _ => test("buildTarget/compile - reports compilation progress") {
val buildTarget = buildTargetUri("runAndTest", "Compile") val buildTarget = buildTargetUri("runAndTest", "Compile")
compile(buildTarget, id = 33) compile(buildTarget, id = 33)
// This doesn't always come back in 10s on CI. // This doesn't always come back in 10s on CI.
assert(svr.waitForString(60.seconds) { s => assert(svr.waitForString(20.seconds) { s =>
s.contains("build/taskStart") && s.contains("build/taskStart") &&
s.contains(""""message":"Compiling runAndTest"""") s.contains(""""message":"Compiling runAndTest"""")
}) })
assert(svr.waitForString(60.seconds) { s => assert(svr.waitForString(20.seconds) { s =>
s.contains("build/taskProgress") && s.contains("build/taskProgress") &&
s.contains(""""message":"Compiling runAndTest (15%)"""") s.contains(""""message":"Compiling runAndTest (15%)"""")
}) })
assert(svr.waitForString(60.seconds) { s => assert(svr.waitForString(20.seconds) { s =>
s.contains("build/taskProgress") && s.contains("build/taskProgress") &&
s.contains(""""message":"Compiling runAndTest (100%)"""") s.contains(""""message":"Compiling runAndTest (100%)"""")
}) })
assert(svr.waitForString(60.seconds) { s => assert(svr.waitForString(20.seconds) { s =>
s.contains("build/publishDiagnostics") s.contains("build/publishDiagnostics")
s.contains(""""diagnostics":[]""") s.contains(""""diagnostics":[]""")
}) })
assert(svr.waitForString(60.seconds) { s => assert(svr.waitForString(20.seconds) { s =>
s.contains("build/taskFinish") && s.contains("build/taskFinish") &&
s.contains(""""message":"Compiled runAndTest"""") s.contains(""""message":"Compiled runAndTest"""")
}) })
@ -136,7 +133,7 @@ object BuildServerTest extends AbstractServerTest {
test( test(
"buildTarget/compile [diagnostics] don't publish unnecessary for successful compilation case" "buildTarget/compile [diagnostics] don't publish unnecessary for successful compilation case"
) { _ => ) {
val buildTarget = buildTargetUri("diagnostics", "Compile") val buildTarget = buildTargetUri("diagnostics", "Compile")
val mainFile = new File(svr.baseDirectory, "diagnostics/src/main/scala/Diagnostics.scala") val mainFile = new File(svr.baseDirectory, "diagnostics/src/main/scala/Diagnostics.scala")
@ -199,7 +196,7 @@ object BuildServerTest extends AbstractServerTest {
) )
} }
test("buildTarget/compile [diagnostics] clear stale warnings") { _ => test("buildTarget/compile [diagnostics] clear stale warnings") {
val buildTarget = buildTargetUri("diagnostics", "Compile") val buildTarget = buildTargetUri("diagnostics", "Compile")
val testFile = new File(svr.baseDirectory, s"diagnostics/src/main/scala/PatternMatch.scala") val testFile = new File(svr.baseDirectory, s"diagnostics/src/main/scala/PatternMatch.scala")
@ -240,7 +237,7 @@ object BuildServerTest extends AbstractServerTest {
} }
test("buildTarget/scalacOptions") { _ => test("buildTarget/scalacOptions") {
val buildTarget = buildTargetUri("util", "Compile") val buildTarget = buildTargetUri("util", "Compile")
val badBuildTarget = buildTargetUri("badBuildTarget", "Compile") val badBuildTarget = buildTargetUri("badBuildTarget", "Compile")
svr.sendJsonRpc( svr.sendJsonRpc(
@ -255,20 +252,14 @@ object BuildServerTest extends AbstractServerTest {
}) })
} }
test("buildTarget/cleanCache") { _ => test("buildTarget/cleanCache") {
def targetDir = def classFile = svr.baseDirectory.toPath.resolve(
Paths "target/out/jvm/scala-2.13.8/runandtest/classes/main/Main.class"
.get( )
svr.baseDirectory.getAbsoluteFile.toString,
"run-and-test/target/scala-2.13/classes/main"
)
.toFile
val buildTarget = buildTargetUri("runAndTest", "Compile") val buildTarget = buildTargetUri("runAndTest", "Compile")
compile(buildTarget, id = 43) compile(buildTarget, id = 43)
svr.waitFor[BspCompileResult](10.seconds) svr.waitFor[BspCompileResult](10.seconds)
assert(targetDir.list().contains("Main.class")) assert(Files.exists(classFile))
svr.sendJsonRpc( svr.sendJsonRpc(
s"""{ "jsonrpc": "2.0", "id": "44", "method": "buildTarget/cleanCache", "params": { s"""{ "jsonrpc": "2.0", "id": "44", "method": "buildTarget/cleanCache", "params": {
| "targets": [{ "uri": "$buildTarget" }] | "targets": [{ "uri": "$buildTarget" }]
@ -277,10 +268,10 @@ object BuildServerTest extends AbstractServerTest {
assert(processing("buildTarget/cleanCache")) assert(processing("buildTarget/cleanCache"))
val res = svr.waitFor[CleanCacheResult](10.seconds) val res = svr.waitFor[CleanCacheResult](10.seconds)
assert(res.cleaned) assert(res.cleaned)
assert(targetDir.list().isEmpty) assert(Files.notExists(classFile))
} }
test("buildTarget/cleanCache: rebuild project") { _ => test("buildTarget/cleanCache: rebuild project") {
svr.sendJsonRpc( svr.sendJsonRpc(
"""{ "jsonrpc": "2.0", "id": "45", "method": "workspace/buildTargets", "params": {} }""" """{ "jsonrpc": "2.0", "id": "45", "method": "workspace/buildTargets", "params": {} }"""
) )
@ -300,7 +291,7 @@ object BuildServerTest extends AbstractServerTest {
assert(res.cleaned) assert(res.cleaned)
} }
test("workspace/reload") { _ => test("workspace/reload") {
svr.sendJsonRpc( svr.sendJsonRpc(
"""{ "jsonrpc": "2.0", "id": "48", "method": "workspace/reload"}""" """{ "jsonrpc": "2.0", "id": "48", "method": "workspace/reload"}"""
) )
@ -311,23 +302,22 @@ object BuildServerTest extends AbstractServerTest {
}) })
} }
test("workspace/reload: send diagnostic and respond with error") { _ => test("workspace/reload: send diagnostic and respond with error") {
// write an other-build.sbt file that does not compile // write an other-build.sbt file that does not compile
val otherBuildFile = new File(svr.baseDirectory, "other-build.sbt") val otherBuildFile = svr.baseDirectory.toPath.resolve("other-build.sbt")
IO.write( Files.writeString(
otherBuildFile, otherBuildFile,
""" """|val someSettings = Seq(
|val someSettings = Seq( | scalacOptions ++= "-deprecation"
| scalacOptions ++= "-deprecation" |)
|) |""".stripMargin
|""".stripMargin
) )
// reload // reload
reloadWorkspace(id = 52) reloadWorkspace(id = 52)
assert( assert(
svr.waitForString(10.seconds) { s => svr.waitForString(10.seconds) { s =>
s.contains(s""""buildTarget":{"uri":"$metaBuildTarget"}""") && s.contains(s""""buildTarget":{"uri":"$metaBuildTarget"}""") &&
s.contains(s""""textDocument":{"uri":"${otherBuildFile.toPath.toUri}"}""") && s.contains(s""""textDocument":{"uri":"${otherBuildFile.toUri}"}""") &&
s.contains(""""severity":1""") && s.contains(""""severity":1""") &&
s.contains(""""reset":true""") s.contains(""""reset":true""")
} }
@ -337,32 +327,31 @@ object BuildServerTest extends AbstractServerTest {
s.contains(""""id":"52"""") && s.contains(""""id":"52"""") &&
s.contains(""""error"""") && s.contains(""""error"""") &&
s.contains(s""""code":${ErrorCodes.InternalError}""") && s.contains(s""""code":${ErrorCodes.InternalError}""") &&
s.contains("Type error in expression") s.contains("No Append.Values[Seq[String], String] found")
} }
) )
// fix the other-build.sbt file and reload again // fix the other-build.sbt file and reload again
IO.write( Files.writeString(
otherBuildFile, otherBuildFile,
""" """|val someSettings = Seq(
|val someSettings = Seq( | scalacOptions += "-deprecation"
| scalacOptions += "-deprecation" |)
|) |""".stripMargin
|""".stripMargin
) )
reloadWorkspace(id = 52) reloadWorkspace(id = 52)
// assert received an empty diagnostic // assert received an empty diagnostic
assert( assert(
svr.waitForString(10.seconds) { s => svr.waitForString(10.seconds) { s =>
s.contains(s""""buildTarget":{"uri":"$metaBuildTarget"}""") && s.contains(s""""buildTarget":{"uri":"$metaBuildTarget"}""") &&
s.contains(s""""textDocument":{"uri":"${otherBuildFile.toPath.toUri}"}""") && s.contains(s""""textDocument":{"uri":"${otherBuildFile.toUri}"}""") &&
s.contains(""""diagnostics":[]""") && s.contains(""""diagnostics":[]""") &&
s.contains(""""reset":true""") s.contains(""""reset":true""")
} }
) )
IO.delete(otherBuildFile) Files.delete(otherBuildFile)
} }
test("buildTarget/scalaMainClasses") { _ => test("buildTarget/scalaMainClasses") {
val buildTarget = buildTargetUri("runAndTest", "Compile") val buildTarget = buildTargetUri("runAndTest", "Compile")
val badBuildTarget = buildTargetUri("badBuildTarget", "Compile") val badBuildTarget = buildTargetUri("badBuildTarget", "Compile")
svr.sendJsonRpc( svr.sendJsonRpc(
@ -377,7 +366,7 @@ object BuildServerTest extends AbstractServerTest {
}) })
} }
test("buildTarget/run") { _ => test("buildTarget/run") {
val buildTarget = buildTargetUri("runAndTest", "Compile") val buildTarget = buildTargetUri("runAndTest", "Compile")
svr.sendJsonRpc( svr.sendJsonRpc(
s"""{ "jsonrpc": "2.0", "id": "64", "method": "buildTarget/run", "params": { s"""{ "jsonrpc": "2.0", "id": "64", "method": "buildTarget/run", "params": {
@ -397,7 +386,7 @@ object BuildServerTest extends AbstractServerTest {
}) })
} }
test("buildTarget/jvmRunEnvironment") { _ => test("buildTarget/jvmRunEnvironment") {
val buildTarget = buildTargetUri("runAndTest", "Compile") val buildTarget = buildTargetUri("runAndTest", "Compile")
svr.sendJsonRpc( svr.sendJsonRpc(
s"""|{ "jsonrpc": "2.0", s"""|{ "jsonrpc": "2.0",
@ -418,7 +407,7 @@ object BuildServerTest extends AbstractServerTest {
} }
} }
test("buildTarget/jvmTestEnvironment") { _ => test("buildTarget/jvmTestEnvironment") {
val buildTarget = buildTargetUri("runAndTest", "Test") val buildTarget = buildTargetUri("runAndTest", "Test")
svr.sendJsonRpc( svr.sendJsonRpc(
s"""|{ "jsonrpc": "2.0", s"""|{ "jsonrpc": "2.0",
@ -441,7 +430,7 @@ object BuildServerTest extends AbstractServerTest {
} }
} }
test("buildTarget/scalaTestClasses") { _ => test("buildTarget/scalaTestClasses") {
val buildTarget = buildTargetUri("runAndTest", "Test") val buildTarget = buildTargetUri("runAndTest", "Test")
val badBuildTarget = buildTargetUri("badBuildTarget", "Test") val badBuildTarget = buildTargetUri("badBuildTarget", "Test")
svr.sendJsonRpc( svr.sendJsonRpc(
@ -458,7 +447,7 @@ object BuildServerTest extends AbstractServerTest {
}) })
} }
test("buildTarget/test: run all tests") { _ => test("buildTarget/test: run all tests") {
val buildTarget = buildTargetUri("runAndTest", "Test") val buildTarget = buildTargetUri("runAndTest", "Test")
svr.sendJsonRpc( svr.sendJsonRpc(
s"""{ "jsonrpc": "2.0", "id": "80", "method": "buildTarget/test", "params": { s"""{ "jsonrpc": "2.0", "id": "80", "method": "buildTarget/test", "params": {
@ -472,7 +461,7 @@ object BuildServerTest extends AbstractServerTest {
}) })
} }
test("buildTarget/test: run one test class") { _ => test("buildTarget/test: run one test class") {
val buildTarget = buildTargetUri("runAndTest", "Test") val buildTarget = buildTargetUri("runAndTest", "Test")
svr.sendJsonRpc( svr.sendJsonRpc(
s"""{ "jsonrpc": "2.0", "id": "84", "method": "buildTarget/test", "params": { s"""{ "jsonrpc": "2.0", "id": "84", "method": "buildTarget/test", "params": {
@ -495,7 +484,7 @@ object BuildServerTest extends AbstractServerTest {
}) })
} }
test("buildTarget/compile: report error") { _ => test("buildTarget/compile: report error") {
val buildTarget = buildTargetUri("reportError", "Compile") val buildTarget = buildTargetUri("reportError", "Compile")
compile(buildTarget, id = 88) compile(buildTarget, id = 88)
assert(svr.waitForString(10.seconds) { s => assert(svr.waitForString(10.seconds) { s =>
@ -505,7 +494,7 @@ object BuildServerTest extends AbstractServerTest {
}) })
} }
test("buildTarget/compile: report warning") { _ => test("buildTarget/compile: report warning") {
val buildTarget = buildTargetUri("reportWarning", "Compile") val buildTarget = buildTargetUri("reportWarning", "Compile")
compile(buildTarget, id = 90) compile(buildTarget, id = 90)
assert(svr.waitForString(10.seconds) { s => assert(svr.waitForString(10.seconds) { s =>
@ -515,7 +504,7 @@ object BuildServerTest extends AbstractServerTest {
}) })
} }
test("buildTarget/compile: respond error") { _ => test("buildTarget/compile: respond error") {
val buildTarget = buildTargetUri("respondError", "Compile") val buildTarget = buildTargetUri("respondError", "Compile")
compile(buildTarget, id = 92) compile(buildTarget, id = 92)
assert(svr.waitForString(10.seconds) { s => assert(svr.waitForString(10.seconds) { s =>
@ -526,7 +515,7 @@ object BuildServerTest extends AbstractServerTest {
}) })
} }
test("buildTarget/resources") { _ => test("buildTarget/resources") {
val buildTarget = buildTargetUri("util", "Compile") val buildTarget = buildTargetUri("util", "Compile")
val badBuildTarget = buildTargetUri("badBuildTarget", "Compile") val badBuildTarget = buildTargetUri("badBuildTarget", "Compile")
svr.sendJsonRpc( svr.sendJsonRpc(
@ -540,7 +529,7 @@ object BuildServerTest extends AbstractServerTest {
}) })
} }
test("buildTarget/outputPaths") { _ => test("buildTarget/outputPaths") {
val buildTarget = buildTargetUri("util", "Compile") val buildTarget = buildTargetUri("util", "Compile")
val badBuildTarget = buildTargetUri("badBuildTarget", "Compile") val badBuildTarget = buildTargetUri("badBuildTarget", "Compile")
svr.sendJsonRpc( svr.sendJsonRpc(

View File

@ -32,6 +32,10 @@ trait AbstractServerTest extends AnyFunSuite with BeforeAndAfterAll {
def testDirectory: String def testDirectory: String
def testPath: Path = temp.toPath.resolve(testDirectory) def testPath: Path = temp.toPath.resolve(testDirectory)
def sbtVersion = sys.props
.get("sbt.server.version")
.getOrElse(throw new IllegalStateException("No server version was specified."))
private val targetDir: File = { private val targetDir: File = {
val p0 = new File("..").getAbsoluteFile.getCanonicalFile / "target" val p0 = new File("..").getAbsoluteFile.getCanonicalFile / "target"
val p1 = new File("target").getAbsoluteFile val p1 = new File("target").getAbsoluteFile