diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 62cbe5f4e..136731274 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -126,7 +126,7 @@ jobs: ./sbt -v --client "Test/compile" ./sbt -v --client publishLocal ./sbt -v --client test - # ./sbt -v --client "serverTestProj/test" + ./sbt -v --client "serverTestProj/test" # ./sbt -v --client doc ./sbt -v --client "all $UTIL_TESTS" ./sbt -v --client ++2.13.x diff --git a/.gitignore b/.gitignore index d2a0e91e5..8b8d533f0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,12 +1,11 @@ target/ __pycache__ -out node_modules vscode-sbt-scala/client/server npm-debug.log *.vsix *_pid*.log -!sbt/src/server-test/completions/target +!server-test/src/server-test/completions/target .big .idea .bloop diff --git a/build.sbt b/build.sbt index 7f1f76c39..1b0903b51 100644 --- a/build.sbt +++ b/build.sbt @@ -995,7 +995,6 @@ lazy val serverTestProj = (project in file("server-test")) .dependsOn(sbtProj % "compile->test", scriptedSbtReduxProj % "compile->test") .settings( testedBaseSettings, - bspEnabled := false, publish / skip := true, // make server tests serial Test / watchTriggers += baseDirectory.value.toGlob / "src" / "server-test" / **, diff --git a/buildfile/src/main/scala/sbt/internal/Eval.scala b/buildfile/src/main/scala/sbt/internal/Eval.scala index a5cec43b5..e22a5d10b 100644 --- a/buildfile/src/main/scala/sbt/internal/Eval.scala +++ b/buildfile/src/main/scala/sbt/internal/Eval.scala @@ -7,7 +7,6 @@ import dotty.tools.dotc.CompilationUnit import dotty.tools.dotc.core.Contexts.{ atPhase, Context } import dotty.tools.dotc.core.{ Flags, Names, Phases, Symbols, Types } import dotty.tools.dotc.Driver -import dotty.tools.dotc.reporting.Reporter import dotty.tools.dotc.Run import dotty.tools.dotc.util.SourceFile import dotty.tools.io.{ PlainDirectory, Directory, VirtualDirectory, VirtualFile } @@ -31,7 +30,7 @@ class Eval( nonCpOptions: Seq[String], classpath: Seq[Path], backingDir: Option[Path], - mkReporter: Option[() => Reporter] + mkReporter: Option[() => EvalReporter] ): import Eval.* @@ -46,9 +45,9 @@ class Eval( .map(_.toString) .mkString(":") private lazy val driver: EvalDriver = new EvalDriver - private lazy val reporter = mkReporter match - case Some(fn) => fn() - case None => EvalReporter.store + private lazy val reporter: EvalReporter = mkReporter match + case Some(f) => f() + case None => EvalReporter.store final class EvalDriver extends Driver: val compileCtx0 = initCtx.fresh @@ -83,6 +82,7 @@ class Eval( line: Int ): EvalResult = val ev = new EvalType[String]: + override def sourceName: String = srcName override def makeSource(moduleName: String): SourceFile = val returnType = tpeName match case Some(tpe) => s": $tpe" @@ -148,6 +148,7 @@ class Eval( require(definitions.nonEmpty, "definitions to evaluate cannot be empty.") val extraHash0 = extraHash val ev = new EvalType[Seq[String]]: + override def sourceName: String = srcName override def makeSource(moduleName: String): SourceFile = val header = imports.strings.mkString("\n") + @@ -176,7 +177,6 @@ class Eval( StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING ) - override def extraHash: String = extraHash0 val inter = evalCommon[Seq[String]](definitions.map(_._1), imports, tpeName = Some(""), ev) @@ -203,13 +203,16 @@ class Eval( val d = digester.digest() val hash = Hash.toHex(d) val moduleName = makeModuleName(hash) - val (extra, loader) = backingDir match - case Some(backing) if classExists(backing, moduleName) => - val loader = (parent: ClassLoader) => - (new URLClassLoader(Array(backing.toUri.toURL), parent): ClassLoader) - val extra = ev.read(cacheFile(backing, moduleName)) - (extra, loader) - case _ => compileAndLoad(ev, moduleName) + val (extra, loader) = + try + backingDir match + case Some(backing) if classExists(backing, moduleName) => + val loader = (parent: ClassLoader) => + (new URLClassLoader(Array(backing.toUri.toURL), parent): ClassLoader) + val extra = ev.read(cacheFile(backing, moduleName)) + (extra, loader) + case _ => compileAndLoad(ev, moduleName) + finally reporter.finalReport(ev.sourceName) val generatedFiles = getGeneratedFiles(moduleName) EvalIntermediate( extra = extra, @@ -277,19 +280,19 @@ object Eval: def apply(): Eval = new Eval(Nil, currentClasspath, None, None) - def apply(mkReporter: () => Reporter): Eval = + def apply(mkReporter: () => EvalReporter): Eval = new Eval(Nil, currentClasspath, None, Some(mkReporter)) def apply( backingDir: Path, - mkReporter: () => Reporter, + mkReporter: () => EvalReporter, ): Eval = new Eval(Nil, currentClasspath, Some(backingDir), Some(mkReporter)) def apply( nonCpOptions: Seq[String], backingDir: Path, - mkReporter: () => Reporter, + mkReporter: () => EvalReporter, ): Eval = new Eval(nonCpOptions, currentClasspath, Some(backingDir), Some(mkReporter)) @@ -326,6 +329,7 @@ object Eval: end EvalSourceFile trait EvalType[A]: + def sourceName: String def makeSource(moduleName: String): SourceFile /** Extracts additional information after the compilation unit is evaluated. */ diff --git a/buildfile/src/main/scala/sbt/internal/EvalReporter.scala b/buildfile/src/main/scala/sbt/internal/EvalReporter.scala index d75890805..c0da90f1c 100644 --- a/buildfile/src/main/scala/sbt/internal/EvalReporter.scala +++ b/buildfile/src/main/scala/sbt/internal/EvalReporter.scala @@ -7,7 +7,14 @@ import dotty.tools.dotc.reporting.Diagnostic import dotty.tools.dotc.reporting.Reporter import dotty.tools.dotc.reporting.StoreReporter -abstract class EvalReporter extends Reporter +abstract class EvalReporter extends Reporter: + /** + * Send a final report to clear out the outdated diagnostics. + * @param sourceName the source path of a build.sbt file + */ + def finalReport(sourceName: String): Unit + + def log(msg: String): Unit = () object EvalReporter: def console: EvalReporter = ForwardingReporter(ConsoleReporter()) @@ -16,4 +23,6 @@ end EvalReporter class ForwardingReporter(delegate: Reporter) extends EvalReporter: def doReport(dia: Diagnostic)(using Context): Unit = delegate.doReport(dia) + + def finalReport(sourceName: String): Unit = () end ForwardingReporter diff --git a/buildfile/src/main/scala/sbt/internal/EvaluateConfigurations.scala b/buildfile/src/main/scala/sbt/internal/EvaluateConfigurations.scala index 5df534590..b74e4b831 100644 --- a/buildfile/src/main/scala/sbt/internal/EvaluateConfigurations.scala +++ b/buildfile/src/main/scala/sbt/internal/EvaluateConfigurations.scala @@ -26,6 +26,7 @@ import sbt.SlashSyntax0.* import sbt.internal.parser.SbtParser import sbt.io.IO import scala.jdk.CollectionConverters.* +import xsbti.PathBasedFile import xsbti.VirtualFile import xsbti.VirtualFileRef @@ -151,7 +152,9 @@ private[sbt] object EvaluateConfigurations { ): LazyClassLoaded[LoadedSbtFile] = { // TODO - Store the file on the LoadedSbtFile (or the parent dir) so we can accurately do // detection for which project project manipulations should be applied. - val name = file.id + val name = file match + case file: PathBasedFile => file.toPath.toString + case file => file.id val parsed = parseConfiguration(file, lines, imports, offset) val (importDefs, definitions) = if (parsed.definitions.isEmpty) (Nil, DefinedSbtValues.empty) diff --git a/main-command/src/main/scala/sbt/Command.scala b/main-command/src/main/scala/sbt/Command.scala index 45e7dce06..72deb14dd 100644 --- a/main-command/src/main/scala/sbt/Command.scala +++ b/main-command/src/main/scala/sbt/Command.scala @@ -152,7 +152,7 @@ object Command { def combine(cmds: Seq[Command]): State => Parser[() => State] = { val (simple, arbs) = separateCommands(cmds) state => - arbs.map(_ parser state).foldLeft(simpleParser(simple)(state))(_ | _) + arbs.map(_.parser(state)).foldLeft(simpleParser(simple)(state))(_ | _) } private[this] def separateCommands( @@ -188,7 +188,7 @@ object Command { else parse(command, state.nonMultiParser)) match { case Right(s) => s() // apply command. command side effects happen here case Left(errMsg) => - state.log error errMsg + state.log.error(errMsg) state.fail } } diff --git a/main-settings/src/main/scala/sbt/Structure.scala b/main-settings/src/main/scala/sbt/Structure.scala index 5579b520c..3e507179e 100644 --- a/main-settings/src/main/scala/sbt/Structure.scala +++ b/main-settings/src/main/scala/sbt/Structure.scala @@ -403,8 +403,6 @@ object Scoped: )((thisTask, deps) => thisTask.dependsOn(deps: _*)) def failure: Initialize[Task[Incomplete]] = init(_.failure) def result: Initialize[Task[Result[A1]]] = init(_.result) - def xtriggeredBy[A2](tasks: Initialize[Task[A2]]*): Initialize[Task[A1]] = - nonLocal(tasks.toSeq.asInstanceOf[Seq[AnyInitTask]], Def.triggeredBy) def triggeredBy[A2](tasks: Initialize[Task[A2]]*): Initialize[Task[A1]] = nonLocal(tasks.toSeq.asInstanceOf[Seq[AnyInitTask]], Def.triggeredBy) def runBefore[A2](tasks: Initialize[Task[A2]]*): Initialize[Task[A1]] = diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index 28a3b0a30..b78b94a63 100644 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -106,9 +106,11 @@ import sbt.internal.inc.{ MixedAnalyzingCompiler, ScalaInstance } -import xsbti.{ CrossValue, HashedVirtualFileRef, VirtualFile, VirtualFileRef } +import sbt.internal.io.Retry +import xsbti.{ CompileFailed, CrossValue, HashedVirtualFileRef, VirtualFile, VirtualFileRef } import xsbti.compile.{ AnalysisContents, + AnalysisStore, ClassFileManagerType, ClasspathOptionsUtil, CompileAnalysis, @@ -1005,7 +1007,7 @@ object Defaults extends BuildCommon { discoveredMainClasses := compile .map(discoverMainClasses) .storeAs(discoveredMainClasses) - .xtriggeredBy(compile) + .triggeredBy(compile) .value, discoveredSbtPlugins := discoverSbtPluginNames.value, // This fork options, scoped to the configuration is used for tests @@ -1266,9 +1268,11 @@ object Defaults extends BuildCommon { testFrameworks.value.flatMap(f => f.create(loader, log).map(x => (f, x))).toMap }, definedTests := detectTests.value, - definedTestNames := (definedTests map (_.map( - _.name - ).distinct) storeAs definedTestNames triggeredBy compile).value, + definedTestNames := definedTests + .map(_.map(_.name).distinct) + .storeAs(definedTestNames) + .triggeredBy(compile) + .value, testQuick / testFilter := testQuickFilter.value, executeTests := { import sbt.TupleSyntax.* @@ -2393,11 +2397,10 @@ object Defaults extends BuildCommon { */ private[sbt] def compileScalaBackendTask: Initialize[Task[CompileResult]] = Def.task { val setup: Setup = compileIncSetup.value - val useBinary: Boolean = enableBinaryCompileAnalysis.value val _ = compileIncremental.value val exportP = exportPipelining.value // Save analysis midway if pipelining is enabled - val store = MixedAnalyzingCompiler.staticCachedStore(setup.cachePath, !useBinary) + val store = analysisStore val contents = store.unsafeGet() if (exportP) { // this stores the eary analysis (again) in case the subproject contains a macro @@ -2422,9 +2425,7 @@ object Defaults extends BuildCommon { .debug(s"${name.value}: compileEarly: blocking on earlyOutputPing") earlyOutputPing.await.value }) { - val useBinary: Boolean = enableBinaryCompileAnalysis.value - val store = - MixedAnalyzingCompiler.staticCachedStore(earlyCompileAnalysisFile.value.toPath, !useBinary) + val store = earlyAnalysisStore store.get.toOption match { case Some(contents) => contents.getAnalysis case _ => Analysis.empty @@ -2436,13 +2437,11 @@ object Defaults extends BuildCommon { def compileTask: Initialize[Task[CompileAnalysis]] = Def.task { val setup: Setup = compileIncSetup.value - val useBinary: Boolean = enableBinaryCompileAnalysis.value + val store = analysisStore val c = fileConverter.value // TODO - expose bytecode manipulation phase. val analysisResult: CompileResult = manipulateBytecode.value if (analysisResult.hasModified) { - val store = - MixedAnalyzingCompiler.staticCachedStore(setup.cacheFile.toPath, !useBinary) val contents = AnalysisContents.create(analysisResult.analysis(), analysisResult.setup()) store.set(contents) } @@ -2455,73 +2454,72 @@ object Defaults extends BuildCommon { analysis } - def compileIncrementalTaskSettings = - inTask(compileIncremental)( - Seq( - (TaskZero / compileIncremental) := (Def - .cachedTask { - val s = streams.value - val ci = (compile / compileInputs).value - // This is a cacheable version - val ci2 = (compile / compileInputs2).value - val ping = (TaskZero / earlyOutputPing).value - val reporter = (compile / bspReporter).value - val setup: Setup = (TaskZero / compileIncSetup).value - val useBinary: Boolean = enableBinaryCompileAnalysis.value - val c = fileConverter.value - val analysisResult: CompileResult = - BspCompileTask - .compute(bspTargetIdentifier.value, thisProjectRef.value, configuration.value) { - bspTask => - // TODO - Should readAnalysis + saveAnalysis be scoped by the compile task too? - compileIncrementalTaskImpl(bspTask, s, ci, ping, reporter) - } - val analysisOut = c.toVirtualFile(setup.cachePath()) - val store = - MixedAnalyzingCompiler.staticCachedStore(setup.cachePath, !useBinary) - 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) - .value, - packagedArtifact := { - val (hasModified, out) = compileIncremental.value - artifact.value -> out - }, - artifact := artifactSetting.value, - artifactClassifier := Some("noresources"), - artifactPath := artifactPathSetting(artifact).value, - ) + def compileIncrementalTaskSettings = inTask(compileIncremental)( + Seq( + (TaskZero / compileIncremental) := { + val bspTask = (compile / bspCompileTask).value + val result = cachedCompileIncrementalTask.result.value + val reporter = (compile / bspReporter).value + val store = analysisStore + val ci = (compile / compileInputs).value + result match + case Result.Value(res) => + val analysis = store.unsafeGet().getAnalysis() + reporter.sendSuccessReport(analysis) + bspTask.notifySuccess(analysis) + res + case Result.Inc(cause) => + val compileFailed = cause.directCause.collect { case c: CompileFailed => c } + reporter.sendFailureReport(ci.options.sources) + bspTask.notifyFailure(compileFailed) + throw cause + }, + 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[sbt] def compileJavaTask: Initialize[Task[CompileResult]] = Def.task { @@ -2549,8 +2547,7 @@ object Defaults extends BuildCommon { task: BspCompileTask, s: TaskStreams, ci: Inputs, - promise: PromiseWrap[Boolean], - reporter: BuildServerReporter, + promise: PromiseWrap[Boolean] ): CompileResult = { lazy val x = s.text(ExportStream) def onArgs(cs: Compilers) = @@ -2565,16 +2562,12 @@ object Defaults extends BuildCommon { val compilers: Compilers = ci.compilers val setup: Setup = ci.setup val i = ci.withCompilers(onArgs(compilers)).withSetup(onProgress(setup)) - try - val result = incCompiler.compile(i, s.log) - reporter.sendSuccessReport(result.getAnalysis) - result + try incCompiler.compile(i, s.log) catch case e: Throwable => if !promise.isCompleted then promise.failure(e) ConcurrentRestrictions.cancelAllSentinels() - reporter.sendFailureReport(ci.options.sources) throw e finally x.close() // workaround for #937 } @@ -2591,12 +2584,8 @@ object Defaults extends BuildCommon { override def definesClass(classpathEntry: VirtualFile): DefinesClass = cachedPerEntryDefinesClassLookup(classpathEntry) val extra = extraIncOptions.value.map(t2) - val useBinary: Boolean = enableBinaryCompileAnalysis.value - val eapath = earlyCompileAnalysisFile.value.toPath - val eaOpt = - if exportPipelining.value then - Some(MixedAnalyzingCompiler.staticCachedStore(eapath, !useBinary)) - else None + val store = earlyAnalysisStore + val eaOpt = if exportPipelining.value then Some(store) else None Setup.of( lookup, (compile / skip).value, @@ -2672,6 +2661,8 @@ object Defaults extends BuildCommon { javacOptions.value.toVector, ) }, + bspCompileTask := + BspCompileTask.start(bspTargetIdentifier.value, thisProjectRef.value, configuration.value) ) } @@ -2698,8 +2689,7 @@ object Defaults extends BuildCommon { def compileAnalysisSettings: Seq[Setting[_]] = Seq( previousCompile := { val setup = compileIncSetup.value - val useBinary: Boolean = enableBinaryCompileAnalysis.value - val store = MixedAnalyzingCompiler.staticCachedStore(setup.cacheFile.toPath, !useBinary) + val store = analysisStore val prev = store.get().toOption match { case Some(contents) => val analysis = Option(contents.getAnalysis).toOptional @@ -2711,6 +2701,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.task { val analysis = compile.value match { case a: Analysis => a } @@ -4828,7 +4830,7 @@ trait BuildExtra extends BuildCommon with DefExtra { } } }) - .value + .evaluated ) ++ inTask(scoped)((config / forkOptions) := forkOptionsTask.value) } diff --git a/main/src/main/scala/sbt/Keys.scala b/main/src/main/scala/sbt/Keys.scala index efc90e81a..c9c044a67 100644 --- a/main/src/main/scala/sbt/Keys.scala +++ b/main/src/main/scala/sbt/Keys.scala @@ -26,7 +26,7 @@ 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.server.{ BspCompileTask, BuildServerReporter, ServerHandler } import sbt.internal.util.{ AttributeKey, ProgressState, SourcePosition } import sbt.internal.util.StringAttributeKey import sbt.io._ @@ -44,6 +44,7 @@ import xsbti.compile.analysis.ReadStamps import scala.concurrent.duration.{ Duration, FiniteDuration } import scala.xml.{ NodeSeq, Node => XNode } + // format: off object Keys { @@ -438,6 +439,7 @@ object Keys { val bspBuildTargetOutputPathsItem = taskKey[OutputPathsItem]("").withRank(DTask) val bspBuildTargetCompile = inputKey[Unit]("").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 bspBuildTargetRun = inputKey[Unit]("Corresponds to buildTarget/run request").withRank(DTask) val bspBuildTargetCleanCache = inputKey[Unit]("Corresponds to buildTarget/cleanCache request").withRank(DTask) diff --git a/main/src/main/scala/sbt/RemoteCache.scala b/main/src/main/scala/sbt/RemoteCache.scala index fcd162011..91801f920 100644 --- a/main/src/main/scala/sbt/RemoteCache.scala +++ b/main/src/main/scala/sbt/RemoteCache.scala @@ -73,8 +73,8 @@ object RemoteCache { Def._outputDirectory = Some(outDir) val caches = s.get(BasicKeys.cacheStores) caches match - case Some(xs) => Def._cacheStore = AggregateActionCacheStore(xs) - case None => + case Some(xs) if xs.nonEmpty => Def._cacheStore = AggregateActionCacheStore(xs) + case _ => val tempDiskCache = (s.baseDir / "target" / "bootcache").toPath() Def._cacheStore = DiskActionCacheStore(tempDiskCache) diff --git a/main/src/main/scala/sbt/internal/Act.scala b/main/src/main/scala/sbt/internal/Act.scala index 9936d1bcf..ee9684a49 100644 --- a/main/src/main/scala/sbt/internal/Act.scala +++ b/main/src/main/scala/sbt/internal/Act.scala @@ -103,7 +103,7 @@ object Act { keyMap: Map[String, AttributeKey[_]], data: Settings[Scope] ): Parser[ParsedKey] = - scopedKeyFull(index, current, defaultConfigs, keyMap) flatMap { choices => + scopedKeyFull(index, current, defaultConfigs, keyMap).flatMap { choices => select(choices, data)(showRelativeKey2(current)) } @@ -355,7 +355,7 @@ object Act { val normKeys = taskKeys(_.label) val valid = allKnown ++ normKeys 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( value(keyP).map(_ -> slashSeq) @@ -515,7 +515,7 @@ object Act { } } action match { - case SingleAction => akp flatMap evaluate + case SingleAction => akp.flatMap(evaluate) case ShowAction | PrintAction | MultiAction => rep1sep(akp, token(Space)) flatMap { pairs => val flat: mutable.ListBuffer[(ScopedKey[_], Seq[String])] = mutable.ListBuffer.empty diff --git a/main/src/main/scala/sbt/internal/Load.scala b/main/src/main/scala/sbt/internal/Load.scala index dbb7ca910..74a012b69 100755 --- a/main/src/main/scala/sbt/internal/Load.scala +++ b/main/src/main/scala/sbt/internal/Load.scala @@ -21,6 +21,7 @@ import sbt.internal.inc.{ MappedFileConverter, ScalaInstance, ZincLmUtil, ZincUt import sbt.internal.util.Attributed.data import sbt.internal.util.Types.const import sbt.internal.util.{ Attributed, Settings } +import sbt.internal.server.BuildServerEvalReporter import sbt.io.{ GlobFilter, IO, Path } import sbt.librarymanagement.ivy.{ InlineIvyConfiguration, IvyDependencyResolution, IvyPaths } import sbt.librarymanagement.{ Configuration, Configurations, Resolver } @@ -33,7 +34,6 @@ import java.net.URI import java.nio.file.{ Path, Paths } import scala.annotation.{ nowarn, tailrec } import scala.collection.mutable -// import scala.tools.nsc.reporters.ConsoleReporter private[sbt] object Load { // note that there is State passed in but not pulled out @@ -78,7 +78,7 @@ private[sbt] object Load { "JAVA_HOME" -> javaHome, ) val loader = getClass.getClassLoader - val converter = MappedFileConverter(rootPaths, false) + val converter = MappedFileConverter(rootPaths, true) val cp0 = provider.mainClasspath.toIndexedSeq ++ scalaProvider.jars.toIndexedSeq val classpath = Attributed.blankSeq( cp0.map(_.toPath).map(p => converter.toVirtualFile(p): HashedVirtualFileRef) @@ -469,7 +469,7 @@ private[sbt] object Load { nonCpOptions = options, classpath = classpath, backingDir = Option(evalOutputDirectory(base).toPath()), - mkReporter = Option(() => (mkReporter(): dotty.tools.dotc.reporting.Reporter)), + mkReporter = Option(() => mkReporter()), ) /** @@ -749,14 +749,10 @@ 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) { - def mkReporter() = EvalReporter.console - // todo: - // 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)) - // } + def mkReporter(): EvalReporter = plugs.pluginData.buildTarget match { + case None => EvalReporter.console + case Some(buildTarget) => new BuildServerEvalReporter(buildTarget, EvalReporter.console) + } mkEval( classpath = plugs.classpath.map(converter.toPath), defDir, diff --git a/main/src/main/scala/sbt/internal/server/BspCompileTask.scala b/main/src/main/scala/sbt/internal/server/BspCompileTask.scala index aa13f0629..877c0b58a 100644 --- a/main/src/main/scala/sbt/internal/server/BspCompileTask.scala +++ b/main/src/main/scala/sbt/internal/server/BspCompileTask.scala @@ -9,57 +9,23 @@ package sbt.internal.server import sbt._ import sbt.internal.bsp._ -import sbt.internal.io.Retry -import sbt.internal.server.BspCompileTask.{ compileReport, exchange } import sbt.librarymanagement.Configuration import sjsonnew.support.scalajson.unsafe.Converter -import xsbti.compile.CompileResult +import xsbti.compile.CompileAnalysis import xsbti.{ CompileFailed, Problem, Severity } -import scala.util.control.NonFatal - object BspCompileTask { - private lazy val exchange = StandardMain.exchange - def compute(targetId: BuildTargetIdentifier, project: ProjectRef, config: Configuration)( - 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( + def start( targetId: BuildTargetIdentifier, project: ProjectRef, config: Configuration ): BspCompileTask = { val taskId = TaskId(BuildServerTasks.uniqueId, Vector()) val targetName = BuildTargetName.fromScope(project.project, config.name) - BspCompileTask(targetId, targetName, taskId, System.currentTimeMillis()) - } - - 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)) + val task = BspCompileTask(targetId, targetName, taskId, System.currentTimeMillis()) + task.notifyStart() + task } } @@ -75,16 +41,16 @@ case class BspCompileTask private ( val message = s"Compiling $targetName" val data = Converter.toJsonUnsafe(CompileTask(targetId)) 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.* val endTimeMillis = System.currentTimeMillis() 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 report = compileReport(problems, targetId, elapsedTimeMillis) + val report = compileReport(problems, elapsedTimeMillis) val params = TaskFinishParams( id, endTimeMillis, @@ -93,7 +59,7 @@ case class BspCompileTask private ( "compile-report", Converter.toJsonUnsafe(report) ) - exchange.notifyEvent("build/taskFinish", params) + StandardMain.exchange.notifyEvent("build/taskFinish", params) } private[sbt] def notifyProgress(percentage: Int, total: Int): Unit = { @@ -110,14 +76,14 @@ case class BspCompileTask private ( Some("compile-progress"), Some(data) ) - exchange.notifyEvent("build/taskProgress", params) + StandardMain.exchange.notifyEvent("build/taskProgress", params) } private[sbt] def notifyFailure(cause: Option[CompileFailed]): Unit = { val endTimeMillis = System.currentTimeMillis() val elapsedTimeMillis = endTimeMillis - startTimeMillis val problems = cause.map(_.problems().toSeq).getOrElse(Seq.empty[Problem]) - val report = compileReport(problems, targetId, elapsedTimeMillis) + val report = compileReport(problems, elapsedTimeMillis) val params = TaskFinishParams( id, endTimeMillis, @@ -126,6 +92,13 @@ case class BspCompileTask private ( "compile-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)) } } diff --git a/main/src/main/scala/sbt/internal/server/BuildServerEvalReporter.scala b/main/src/main/scala/sbt/internal/server/BuildServerEvalReporter.scala index 7cd55737e..95210f80f 100644 --- a/main/src/main/scala/sbt/internal/server/BuildServerEvalReporter.scala +++ b/main/src/main/scala/sbt/internal/server/BuildServerEvalReporter.scala @@ -8,35 +8,34 @@ package sbt.internal.server import dotty.tools.dotc.core.Contexts.Context -import dotty.tools.dotc.reporting.{ Diagnostic => ScalaDiagnostic } import dotty.tools.dotc.reporting.Reporter -// import sbt.StandardMain.exchange -import sbt.internal.ForwardingReporter +import dotty.tools.dotc.reporting.{ Diagnostic => ScalaDiagnostic } +import dotty.tools.dotc.util.SourcePosition +import sbt.StandardMain.exchange +import sbt.internal.EvalReporter import sbt.internal.bsp -import sbt.internal.bsp.{ - BuildTargetIdentifier, - Diagnostic, - // DiagnosticSeverity, - // PublishDiagnosticsParams, - // Range, - // TextDocumentIdentifier -} +import sbt.internal.bsp.BuildTargetIdentifier +import sbt.internal.bsp.Diagnostic +import sbt.internal.bsp.DiagnosticSeverity +import sbt.internal.bsp.PublishDiagnosticsParams +import sbt.internal.bsp.Range +import sbt.internal.bsp.TextDocumentIdentifier +import sbt.internal.bsp.codec.JsonProtocol._ import java.nio.file.Path +import java.nio.file.Paths import scala.collection.mutable class BuildServerEvalReporter(buildTarget: BuildTargetIdentifier, delegate: Reporter) - extends ForwardingReporter(delegate): + extends EvalReporter: private val problemsByFile = mutable.Map[Path, Vector[Diagnostic]]() override def doReport(dia: ScalaDiagnostic)(using Context): 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) + if (dia.pos.exists) { + val filePath = Paths.get(dia.pos.source.file.path) + val range = convertToRange(dia.pos) + val bspSeverity = convertToBsp(dia.level) + val diagnostic = Diagnostic(range, bspSeverity, None, Option("sbt"), dia.msg.message) problemsByFile(filePath) = problemsByFile.getOrElse(filePath, Vector()) :+ diagnostic val params = PublishDiagnosticsParams( TextDocumentIdentifier(filePath.toUri), @@ -46,51 +45,40 @@ class BuildServerEvalReporter(buildTarget: BuildTargetIdentifier, delegate: Repo reset = false ) exchange.notifyEvent("build/publishDiagnostics", params) + delegate.doReport(dia) } - */ - super.doReport(dia) } - /* - def finalReport(sourceName: String): Unit = { + 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) - } + 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] = { + private def convertToBsp(severity: Int): Option[Long] = { val result = severity match { - case Reporter.INFO => DiagnosticSeverity.Information - case Reporter.WARNING => DiagnosticSeverity.Warning - case Reporter.ERROR => DiagnosticSeverity.Error + case dotty.tools.dotc.interfaces.Diagnostic.INFO => DiagnosticSeverity.Information + case dotty.tools.dotc.interfaces.Diagnostic.WARNING => DiagnosticSeverity.Warning + case dotty.tools.dotc.interfaces.Diagnostic.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 - } + private def convertToRange(pos: SourcePosition): Range = { + 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) + Range( + bsp.Position(startLine.toLong, startChar.toLong), + bsp.Position(endLine.toLong, endChar.toLong) + ) } - */ end BuildServerEvalReporter diff --git a/main/src/main/scala/sbt/internal/server/BuildServerProtocol.scala b/main/src/main/scala/sbt/internal/server/BuildServerProtocol.scala index a6645e7b3..3e29b1fe6 100644 --- a/main/src/main/scala/sbt/internal/server/BuildServerProtocol.scala +++ b/main/src/main/scala/sbt/internal/server/BuildServerProtocol.scala @@ -38,9 +38,8 @@ import java.io.File import java.util.concurrent.atomic.AtomicBoolean import scala.collection.mutable -// import scala.annotation.nowarn import scala.util.control.NonFatal -import scala.util.{ Failure, Success } +import scala.util.{ Try, Failure, Success } import scala.annotation.nowarn import sbt.testing.Framework @@ -62,14 +61,10 @@ object BuildServerProtocol { private val bspReload = "bspReload" - private lazy val targetIdentifierParser: Parser[Seq[BuildTargetIdentifier]] = + private val targetIdentifierParser: Parser[Seq[BuildTargetIdentifier]] = Def .spaceDelimited() - .map { xs => - xs.map { uri => - BuildTargetIdentifier(URI.create(uri)) - } - } + .map(xs => xs.map(uri => BuildTargetIdentifier(URI.create(uri)))) lazy val commands: Seq[Command] = Seq( Command.single(bspReload) { (state, reqId) => @@ -103,7 +98,7 @@ object BuildServerProtocol { bspSbtEnabled := true, bspFullWorkspace := bspFullWorkspaceSetting.value, bspWorkspace := bspFullWorkspace.value.scopes, - bspWorkspaceBuildTargets := (Def + bspWorkspaceBuildTargets := Def .task { val workspace = Keys.bspFullWorkspace.value val state = Keys.state.value @@ -121,186 +116,137 @@ object BuildServerProtocol { state.respondEvent(WorkspaceBuildTargetsResult(successfulBuildTargets.toVector)) successfulBuildTargets } - }) + } .value, // https://github.com/build-server-protocol/build-server-protocol/blob/master/docs/specification.md#build-target-sources-request - bspBuildTargetSources := (Def - .input((s: State) => targetIdentifierParser) - .flatMapTask { targets => - val s = state.value - // val targets = spaceDelimited().parsed.map(uri => BuildTargetIdentifier(URI.create(uri))) - val workspace = bspFullWorkspace.value.filter(targets) - val filter = ScopeFilter.in(workspace.scopes.values.toList) - // run the worker task concurrently - Def.task { - val items = bspBuildTargetSourcesItem.result.all(filter).value - val buildItems = workspace.builds.map { case (id, loadedBuildUnit) => - val base = loadedBuildUnit.localBase - val sbtFiles = configurationSources(base) - val pluginData = loadedBuildUnit.unit.plugins.pluginData - val dirs = pluginData.unmanagedSourceDirectories - val sourceFiles = getStandaloneSourceFiles(pluginData.unmanagedSources, dirs) - val managedDirs = pluginData.managedSourceDirectories - val managedSourceFiles = - getStandaloneSourceFiles(pluginData.managedSources, managedDirs) - val items = - dirs.map(toSourceItem(SourceItemKind.Directory, generated = false)) ++ - sourceFiles.map(toSourceItem(SourceItemKind.File, generated = false)) ++ - managedDirs.map(toSourceItem(SourceItemKind.Directory, generated = true)) ++ - 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 := bspInputTask { (workspace, filter) => + val items = bspBuildTargetSourcesItem.result.all(filter).value + val buildItems = workspace.builds.map { case (id, loadedBuildUnit) => + val base = loadedBuildUnit.localBase + val sbtFiles = configurationSources(base) + val pluginData = loadedBuildUnit.unit.plugins.pluginData + val dirs = pluginData.unmanagedSourceDirectories + val sourceFiles = getStandaloneSourceFiles(pluginData.unmanagedSources, dirs) + val managedDirs = pluginData.managedSourceDirectories + val managedSourceFiles = + getStandaloneSourceFiles(pluginData.managedSources, managedDirs) + val items = + dirs.map(toSourceItem(SourceItemKind.Directory, generated = false)) ++ + sourceFiles.map(toSourceItem(SourceItemKind.File, generated = false)) ++ + managedDirs.map(toSourceItem(SourceItemKind.Directory, generated = true)) ++ + 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) + state.value.respondEvent(result) + }.evaluated, bspBuildTargetSources / aggregate := false, - bspBuildTargetResources := (Def - .input((s: State) => targetIdentifierParser) - .flatMapTask { targets => - val s = state.value - 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.result.all(filter).value - val successfulItems = anyOrThrow(items) - val result = ResourcesResult(successfulItems.toVector) - s.respondEvent(result) - } - }) - .value, + bspBuildTargetResources := bspInputTask { (_, filter) => + val items = bspBuildTargetResourcesItem.result.all(filter).value + val successfulItems = anyOrThrow(items) + val result = ResourcesResult(successfulItems.toVector) + state.value.respondEvent(result) + }.evaluated, bspBuildTargetResources / aggregate := false, - bspBuildTargetDependencySources := (Def - .input((s: State) => targetIdentifierParser) - .flatMapTask { targets => - val s = state.value - 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._ - val items = bspBuildTargetDependencySourcesItem.result.all(filter).value - val successfulItems = anyOrThrow(items) - val result = DependencySourcesResult(successfulItems.toVector) - s.respondEvent(result) - } - }) - .value, + bspBuildTargetDependencySources := bspInputTask { (_, filter) => + val items = bspBuildTargetDependencySourcesItem.result.all(filter).value + val successfulItems = anyOrThrow(items) + val result = DependencySourcesResult(successfulItems.toVector) + state.value.respondEvent(result) + }.evaluated, bspBuildTargetDependencySources / aggregate := false, - bspBuildTargetCompile := (Def - .input((s: State) => targetIdentifierParser) - .flatMapTask { targets => - val s: State = state.value - val workspace = bspFullWorkspace.value.filter(targets) - workspace.warnIfBuildsNonEmpty(Method.Compile, s.log) - val filter = ScopeFilter.in(workspace.scopes.values.toList) - Def.task { - val statusCodes = Keys.bspBuildTargetCompileItem.result.all(filter).value - val aggregatedStatusCode = allOrThrow(statusCodes) match { - case Seq() => StatusCode.Success - case codes => codes.max - } - s.respondEvent(BspCompileResult(None, aggregatedStatusCode)) - } - }) - .value, + bspBuildTargetCompile := bspInputTask { (workspace, filter) => + val s = state.value + workspace.warnIfBuildsNonEmpty(Method.Compile, s.log) + val statusCodes = Keys.bspBuildTargetCompileItem.result.all(filter).value + val aggregatedStatusCode = allOrThrow(statusCodes) match { + case Seq() => StatusCode.Success + case codes => codes.max + } + s.respondEvent(BspCompileResult(None, aggregatedStatusCode)) + }.evaluated, + bspBuildTargetOutputPaths := bspInputTask { (_, filter) => + val items = bspBuildTargetOutputPathsItem.result.all(filter).value + val successfulItems = anyOrThrow(items) + val result = OutputPathsResult(successfulItems.toVector) + state.value.respondEvent(result) + }.evaluated, + bspBuildTargetOutputPaths / aggregate := false, bspBuildTargetCompile / aggregate := false, bspBuildTargetTest := bspTestTask.evaluated, bspBuildTargetTest / aggregate := false, - bspBuildTargetCleanCache := (Def - .input((s: State) => targetIdentifierParser) - .flatMapTask { targets => - val s: State = state.value - val workspace = bspFullWorkspace.value.filter(targets) - 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 + bspBuildTargetCleanCache := bspInputTask { (workspace, filter) => + val s = state.value + workspace.warnIfBuildsNonEmpty(Method.CleanCache, s.log) + 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 - // not returned as part of the results. In this case, there's 1 build entry in the workspace, and we're - // checking that the executed results plus this entry is equal to the total number of targets. - // When rebuilding a single module, the root build isn't sent, just the requested targets. - val cleaned = successes + workspace.builds.size == targets.size - s.respondEvent(CleanCacheResult(None, cleaned)) - } - }) - .value, + // When asking to rebuild Project, IntelliJ sends the root build as an additional target, + // however it is not returned as part of the results. We're checking that the number of + // 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. + val cleaned = successes == workspace.scopes.size + s.respondEvent(CleanCacheResult(None, cleaned)) + }.evaluated, bspBuildTargetCleanCache / aggregate := false, - bspBuildTargetScalacOptions := (Def - .input((s: State) => targetIdentifierParser) - .flatMapTask { targets => - val s = state.value - val workspace = bspFullWorkspace.value.filter(targets) - val builds = workspace.builds - - val filter = ScopeFilter.in(workspace.scopes.values.toList) - Def.task { - val items = bspBuildTargetScalacOptionsItem.result.all(filter).value - 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 converter = plugins.pluginData.converter - val classpath = - pluginClasspath.map(converter.toPath).map(_.toFile).map(_.toURI).toVector ++ - (sbtJars).map(_.toURI).toVector - val item = ScalacOptionsItem( - build._1, - scalacOptions.toVector, - classpath, - 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 := bspInputTask { (workspace, filter) => + val items = bspBuildTargetScalacOptionsItem.result.all(filter).value + val appProvider = appConfiguration.value.provider() + val sbtJars = appProvider.mainClasspath() + val buildItems = workspace.builds.map { build => + val plugins: LoadedPlugins = build._2.unit.plugins + val scalacOptions = plugins.pluginData.scalacOptions + val pluginClasspath = plugins.classpath + val converter = plugins.pluginData.converter + val classpath = + pluginClasspath.map(converter.toPath).map(_.toFile).map(_.toURI).toVector ++ + (sbtJars).map(_.toURI).toVector + val item = ScalacOptionsItem( + build._1, + scalacOptions.toVector, + classpath, + new File(build._2.localBase, "project/target").toURI + ) + Result.Value(item) + } + val successfulItems = anyOrThrow(items ++ buildItems) + val result = ScalacOptionsResult(successfulItems.toVector) + state.value.respondEvent(result) + }.evaluated, bspBuildTargetScalacOptions / aggregate := false, - bspScalaTestClasses := (Def - .input((s: State) => targetIdentifierParser) - .flatMapTask { targets => - val s = state.value - 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.result.all(filter).value - val successfulItems = anyOrThrow[Seq[ScalaTestClassesItem]](items).flatten - val result = ScalaTestClassesResult( - items = successfulItems.toVector, - originId = None: Option[String] - ) - s.respondEvent(result) - } - }) - .value, - bspScalaMainClasses := (Def - .input((s: State) => targetIdentifierParser) - .flatMapTask { targets => - val s = state.value - 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.result.all(filter).value - val successfulItems = anyOrThrow(items) - val result = ScalaMainClassesResult(successfulItems.toVector, None) - s.respondEvent(result) - } - }) - .value, + bspBuildTargetJVMRunEnvironment := bspInputTask { (_, filter) => + val items = bspBuildTargetJvmEnvironmentItem.result.all(filter).value + val successfulItems = anyOrThrow(items) + val result = JvmRunEnvironmentResult(successfulItems.toVector, None) + state.value.respondEvent(result) + }.evaluated, + bspBuildTargetJVMRunEnvironment / aggregate := false, + bspBuildTargetJVMTestEnvironment := bspInputTask { (_, filter) => + val items = bspBuildTargetJvmEnvironmentItem.result.all(filter).value + val successfulItems = anyOrThrow(items) + val result = JvmTestEnvironmentResult(successfulItems.toVector, None) + state.value.respondEvent(result) + }.evaluated, + bspBuildTargetJVMTestEnvironment / aggregate := false, + bspScalaTestClasses := bspInputTask { (workspace, filter) => + val s = state.value + val items = bspScalaTestClassesItem.result.all(filter).value + workspace.warnIfBuildsNonEmpty(Method.ScalaTestClasses, s.log) + val successfulItems = anyOrThrow[Seq[ScalaTestClassesItem]](items).flatten + val result = ScalaTestClassesResult( + items = successfulItems.toVector, + originId = None: Option[String] + ) + s.respondEvent(result) + }.evaluated, + bspScalaMainClasses := bspInputTask { (_, filter) => + val items = bspScalaMainClassesItem.result.all(filter).value + val successfulItems = anyOrThrow(items) + val result = ScalaMainClassesResult(successfulItems.toVector, None) + state.value.respondEvent(result) + }.evaluated, bspScalaMainClasses / aggregate := false ) @@ -344,22 +290,6 @@ object BuildServerProtocol { bspBuildTargetCompileItem := bspCompileTask.value, bspBuildTargetRun := bspRunTask.evaluated, 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, bspInternalDependencyConfigurations := internalDependencyConfigurationsSetting.value, bspScalaTestClassesItem := scalaTestClassesTask.value, @@ -755,21 +685,16 @@ object BuildServerProtocol { ) } - private def bspInputTask[T]( - taskImpl: ( - State, - Seq[BuildTargetIdentifier], - BspFullWorkspace, - ScopeFilter - ) => Def.Initialize[Task[T]] + private inline def bspInputTask[T]( + inline taskImpl: (BspFullWorkspace, ScopeFilter) => T ): Def.Initialize[InputTask[T]] = Def - .input((s: State) => targetIdentifierParser) + .input(_ => targetIdentifierParser) .flatMapTask { targets => val s = state.value val workspace: BspFullWorkspace = bspFullWorkspace.value.filter(targets) 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 { @@ -865,12 +790,12 @@ object BuildServerProtocol { } } - private val jsonParser: Parser[JValue] = (Parsers.any.*).map(_.mkString) - .map(JsonParser.parseUnsafe) + private val jsonParser: Parser[Try[JValue]] = Parsers.any.*.map(_.mkString) + .map(JsonParser.parseFromString) private def bspRunTask: Def.Initialize[InputTask[Unit]] = - Def.input((s: State) => jsonParser).flatMapTask { json => - val runParams = Converter.fromJson[RunParams](json).get + Def.input(_ => jsonParser).flatMapTask { json => + val runParams = json.flatMap(Converter.fromJson[RunParams]).get val defaultClass = Keys.mainClass.value val defaultJvmOptions = Keys.javaOptions.value @@ -912,8 +837,8 @@ object BuildServerProtocol { } private def bspTestTask: Def.Initialize[InputTask[Unit]] = - Def.input((s: State) => jsonParser).flatMapTask { json => - val testParams = Converter.fromJson[TestParams](json).get + Def.input(_ => jsonParser).flatMapTask { json => + val testParams = json.flatMap(Converter.fromJson[TestParams]).get val workspace = bspFullWorkspace.value val resultTask: Def.Initialize[Task[Result[Seq[Unit]]]] = testParams.dataKind match { diff --git a/main/src/main/scala/sbt/internal/server/BuildServerReporter.scala b/main/src/main/scala/sbt/internal/server/BuildServerReporter.scala index 3cd0af45f..823a04a6a 100644 --- a/main/src/main/scala/sbt/internal/server/BuildServerReporter.scala +++ b/main/src/main/scala/sbt/internal/server/BuildServerReporter.scala @@ -39,9 +39,7 @@ sealed trait BuildServerReporter extends Reporter { protected def publishDiagnostic(problem: Problem): Unit - def sendSuccessReport( - analysis: CompileAnalysis, - ): Unit + def sendSuccessReport(analysis: CompileAnalysis): Unit def sendFailureReport(sources: Array[VirtualFile]): Unit diff --git a/main/src/main/scala/sbt/internal/server/LanguageServerProtocol.scala b/main/src/main/scala/sbt/internal/server/LanguageServerProtocol.scala index 8279c2fbb..c21b88764 100644 --- a/main/src/main/scala/sbt/internal/server/LanguageServerProtocol.scala +++ b/main/src/main/scala/sbt/internal/server/LanguageServerProtocol.scala @@ -84,7 +84,6 @@ private[sbt] object LanguageServerProtocol { onCancellationRequest(Option(r.id), param) case r: JsonRpcRequestMessage if r.method == "sbt/completion" => - import sbt.protocol.codec.JsonProtocol._ val param = Converter.fromJson[CP](json(r)).get onCompletionRequest(Option(r.id), param) diff --git a/sbt-app/src/sbt-test/actions/run-task/pending b/sbt-app/src/sbt-test/actions/run-task/test similarity index 100% rename from sbt-app/src/sbt-test/actions/run-task/pending rename to sbt-app/src/sbt-test/actions/run-task/test diff --git a/server-test/src/server-test/buildserver/build.sbt b/server-test/src/server-test/buildserver/build.sbt index e41e0192b..b3a6eae15 100644 --- a/server-test/src/server-test/buildserver/build.sbt +++ b/server-test/src/server-test/buildserver/build.sbt @@ -1,6 +1,7 @@ ThisBuild / scalaVersion := "2.13.8" Global / serverLog / logLevel := Level.Debug +Global / cacheStores := Seq.empty lazy val runAndTest = project.in(file("run-and-test")) .settings( diff --git a/server-test/src/server-test/client/build.sbt b/server-test/src/server-test/client/build.sbt index 3225bd76d..31faab953 100644 --- a/server-test/src/server-test/client/build.sbt +++ b/server-test/src/server-test/client/build.sbt @@ -2,6 +2,7 @@ TaskKey[Unit]("willSucceed") := println("success") TaskKey[Unit]("willFail") := { throw new Exception("failed") } +scalaVersion := "2.12.19" libraryDependencies += "org.scalatest" %% "scalatest" % "3.0.8" % "test" TaskKey[Unit]("fooBar") := { () } diff --git a/server-test/src/server-test/completions/build.sbt b/server-test/src/server-test/completions/build.sbt index 90de9d529..c4918e8d5 100644 --- a/server-test/src/server-test/completions/build.sbt +++ b/server-test/src/server-test/completions/build.sbt @@ -1,4 +1,5 @@ val hello = taskKey[Unit]("Say hello") +scalaVersion := "3.3.1" hello := {} diff --git a/server-test/src/server-test/completions/target/streams/test/_global/_global/definedTestNames/data b/server-test/src/server-test/completions/target/out/jvm/scala-3.3.1/completions/streams/test/_global/_global/definedTestNames/data similarity index 100% rename from server-test/src/server-test/completions/target/streams/test/_global/_global/definedTestNames/data rename to server-test/src/server-test/completions/target/out/jvm/scala-3.3.1/completions/streams/test/_global/_global/definedTestNames/data diff --git a/server-test/src/server-test/response/build.sbt b/server-test/src/server-test/response/build.sbt index 9bf691e56..5444110ac 100644 --- a/server-test/src/server-test/response/build.sbt +++ b/server-test/src/server-test/response/build.sbt @@ -48,26 +48,28 @@ lazy val fooClasspath = taskKey[Unit]("") lazy val root = (project in file(".")) .settings( name := "response", - commands += Command.command("fooExport") { s0: State => + commands += Command.command("fooExport") { (s0: State) => val (s1, cp) = s0.unsafeRunTask(Compile / fullClasspath) - s0.respondEvent(cp.map(_.data)) + val converter = s1.setting(fileConverter) + s1.respondEvent(cp.map(a => converter.toPath(a.data))) s1 }, - commands += Command.command("fooFail") { s0: State => + commands += Command.command("fooFail") { (s0: State) => sys.error("fail message") }, - commands += Command.command("fooCustomFail") { s0: State => + commands += Command.command("fooCustomFail") { (s0: State) => import sbt.internal.protocol.JsonRpcResponseError throw JsonRpcResponseError(500, "some error") }, - commands += Command.command("fooNotification") { s0: State => + commands += Command.command("fooNotification") { (s0: State) => import CacheImplicits._ s0.notifyEvent("foo/something", "something") s0 }, fooClasspath := { val s = state.value + val converter = fileConverter.value val cp = (Compile / fullClasspath).value - s.respondEvent(cp.map(_.data)) + s.respondEvent(cp.map(a => converter.toPath(a.data))) } ) diff --git a/server-test/src/test/scala/testpkg/BuildServerTest.scala b/server-test/src/test/scala/testpkg/BuildServerTest.scala index 5765e1649..68f86e30c 100644 --- a/server-test/src/test/scala/testpkg/BuildServerTest.scala +++ b/server-test/src/test/scala/testpkg/BuildServerTest.scala @@ -7,27 +7,27 @@ package testpkg -import sbt.internal.bsp._ +import sbt.internal.bsp.* import sbt.internal.langserver.ErrorCodes import sbt.IO import sbt.internal.protocol.JsonRpcRequestMessage -import sbt.internal.protocol.codec.JsonRPCProtocol._ +import sbt.internal.protocol.codec.JsonRPCProtocol.* import sjsonnew.JsonWriter import sjsonnew.support.scalajson.unsafe.{ CompactPrinter, Converter } import java.io.File import java.net.URI -import java.nio.file.Paths -import scala.concurrent.duration._ +import java.nio.file.Files +import scala.concurrent.duration.* // 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._ override val testDirectory: String = "buildserver" - test("build/initialize") { _ => + test("build/initialize") { initializeRequest() assert(svr.waitForString(10.seconds) { s => (s contains """"id":"8"""") && @@ -36,7 +36,7 @@ object BuildServerTest extends AbstractServerTest { }) } - test("workspace/buildTargets") { _ => + test("workspace/buildTargets") { svr.sendJsonRpc( """{ "jsonrpc": "2.0", "id": "16", "method": "workspace/buildTargets", "params": {} }""" ) @@ -50,7 +50,7 @@ object BuildServerTest extends AbstractServerTest { assert(!result.targets.exists(_.displayName.contains("badBuildTarget"))) } - test("buildTarget/sources") { _ => + test("buildTarget/sources") { val buildTarget = buildTargetUri("util", "Compile") val badBuildTarget = buildTargetUri("badBuildTarget", "Compile") svr.sendJsonRpc(buildTargetSources(24, Seq(buildTarget, badBuildTarget))) @@ -59,7 +59,7 @@ object BuildServerTest extends AbstractServerTest { val sources = s.items.head.sources.map(_.uri) 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") svr.sendJsonRpc(buildTargetSources(25, Seq(buildTarget))) assert(processing("buildTarget/sources")) @@ -73,7 +73,7 @@ object BuildServerTest extends AbstractServerTest { assert(sources.contains(expectedSource)) } - test("buildTarget/sources: sbt") { _ => + test("buildTarget/sources: sbt") { val x = new URI(s"${svr.baseDirectory.getAbsoluteFile.toURI}#buildserver-build") svr.sendJsonRpc(buildTargetSources(26, Seq(x))) assert(processing("buildTarget/sources")) @@ -83,16 +83,15 @@ object BuildServerTest extends AbstractServerTest { "build.sbt", "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-3", + s"project/src/main/scala-sbt-${TestProperties.version}", "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 assert(sources == expectedSources) } - test("buildTarget/compile") { _ => + test("buildTarget/compile") { val buildTarget = buildTargetUri("util", "Compile") compile(buildTarget, id = 32) @@ -102,33 +101,31 @@ object BuildServerTest extends AbstractServerTest { assert(res.statusCode == StatusCode.Success) } - test("buildTarget/compile - reports compilation progress") { _ => + test("buildTarget/compile - reports compilation progress") { val buildTarget = buildTargetUri("runAndTest", "Compile") - compile(buildTarget, id = 33) - // 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(""""message":"Compiling runAndTest"""") }) - assert(svr.waitForString(60.seconds) { s => + assert(svr.waitForString(20.seconds) { s => s.contains("build/taskProgress") && s.contains(""""message":"Compiling runAndTest (15%)"""") }) - assert(svr.waitForString(60.seconds) { s => + assert(svr.waitForString(20.seconds) { s => s.contains("build/taskProgress") && s.contains(""""message":"Compiling runAndTest (100%)"""") }) - assert(svr.waitForString(60.seconds) { s => + assert(svr.waitForString(20.seconds) { s => s.contains("build/publishDiagnostics") s.contains(""""diagnostics":[]""") }) - assert(svr.waitForString(60.seconds) { s => + assert(svr.waitForString(20.seconds) { s => s.contains("build/taskFinish") && s.contains(""""message":"Compiled runAndTest"""") }) @@ -136,7 +133,7 @@ object BuildServerTest extends AbstractServerTest { test( "buildTarget/compile [diagnostics] don't publish unnecessary for successful compilation case" - ) { _ => + ) { val buildTarget = buildTargetUri("diagnostics", "Compile") 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 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 badBuildTarget = buildTargetUri("badBuildTarget", "Compile") svr.sendJsonRpc( @@ -255,20 +252,14 @@ object BuildServerTest extends AbstractServerTest { }) } - test("buildTarget/cleanCache") { _ => - def targetDir = - Paths - .get( - svr.baseDirectory.getAbsoluteFile.toString, - "run-and-test/target/scala-2.13/classes/main" - ) - .toFile - + test("buildTarget/cleanCache") { + def classFile = svr.baseDirectory.toPath.resolve( + "target/out/jvm/scala-2.13.8/runandtest/classes/main/Main.class" + ) val buildTarget = buildTargetUri("runAndTest", "Compile") compile(buildTarget, id = 43) svr.waitFor[BspCompileResult](10.seconds) - assert(targetDir.list().contains("Main.class")) - + assert(Files.exists(classFile)) svr.sendJsonRpc( s"""{ "jsonrpc": "2.0", "id": "44", "method": "buildTarget/cleanCache", "params": { | "targets": [{ "uri": "$buildTarget" }] @@ -277,10 +268,10 @@ object BuildServerTest extends AbstractServerTest { assert(processing("buildTarget/cleanCache")) val res = svr.waitFor[CleanCacheResult](10.seconds) assert(res.cleaned) - assert(targetDir.list().isEmpty) + assert(Files.notExists(classFile)) } - test("buildTarget/cleanCache: rebuild project") { _ => + test("buildTarget/cleanCache: rebuild project") { svr.sendJsonRpc( """{ "jsonrpc": "2.0", "id": "45", "method": "workspace/buildTargets", "params": {} }""" ) @@ -300,7 +291,7 @@ object BuildServerTest extends AbstractServerTest { assert(res.cleaned) } - test("workspace/reload") { _ => + test("workspace/reload") { svr.sendJsonRpc( """{ "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 - val otherBuildFile = new File(svr.baseDirectory, "other-build.sbt") - IO.write( + val otherBuildFile = svr.baseDirectory.toPath.resolve("other-build.sbt") + Files.writeString( otherBuildFile, - """ - |val someSettings = Seq( - | scalacOptions ++= "-deprecation" - |) - |""".stripMargin + """|val someSettings = Seq( + | scalacOptions ++= "-deprecation" + |) + |""".stripMargin ) // reload reloadWorkspace(id = 52) assert( svr.waitForString(10.seconds) { s => 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(""""reset":true""") } @@ -337,32 +327,31 @@ object BuildServerTest extends AbstractServerTest { s.contains(""""id":"52"""") && s.contains(""""error"""") && 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 - IO.write( + Files.writeString( otherBuildFile, - """ - |val someSettings = Seq( - | scalacOptions += "-deprecation" - |) - |""".stripMargin + """|val someSettings = Seq( + | scalacOptions += "-deprecation" + |) + |""".stripMargin ) reloadWorkspace(id = 52) // 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(s""""textDocument":{"uri":"${otherBuildFile.toUri}"}""") && s.contains(""""diagnostics":[]""") && s.contains(""""reset":true""") } ) - IO.delete(otherBuildFile) + Files.delete(otherBuildFile) } - test("buildTarget/scalaMainClasses") { _ => + test("buildTarget/scalaMainClasses") { val buildTarget = buildTargetUri("runAndTest", "Compile") val badBuildTarget = buildTargetUri("badBuildTarget", "Compile") svr.sendJsonRpc( @@ -377,7 +366,7 @@ object BuildServerTest extends AbstractServerTest { }) } - test("buildTarget/run") { _ => + test("buildTarget/run") { val buildTarget = buildTargetUri("runAndTest", "Compile") svr.sendJsonRpc( 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") svr.sendJsonRpc( s"""|{ "jsonrpc": "2.0", @@ -418,7 +407,7 @@ object BuildServerTest extends AbstractServerTest { } } - test("buildTarget/jvmTestEnvironment") { _ => + test("buildTarget/jvmTestEnvironment") { val buildTarget = buildTargetUri("runAndTest", "Test") svr.sendJsonRpc( s"""|{ "jsonrpc": "2.0", @@ -441,7 +430,7 @@ object BuildServerTest extends AbstractServerTest { } } - test("buildTarget/scalaTestClasses") { _ => + test("buildTarget/scalaTestClasses") { val buildTarget = buildTargetUri("runAndTest", "Test") val badBuildTarget = buildTargetUri("badBuildTarget", "Test") 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") svr.sendJsonRpc( 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") svr.sendJsonRpc( 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") compile(buildTarget, id = 88) 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") compile(buildTarget, id = 90) 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") compile(buildTarget, id = 92) 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 badBuildTarget = buildTargetUri("badBuildTarget", "Compile") svr.sendJsonRpc( @@ -540,7 +529,7 @@ object BuildServerTest extends AbstractServerTest { }) } - test("buildTarget/outputPaths") { _ => + test("buildTarget/outputPaths") { val buildTarget = buildTargetUri("util", "Compile") val badBuildTarget = buildTargetUri("badBuildTarget", "Compile") svr.sendJsonRpc( diff --git a/server-test/src/test/scala/testpkg/ClientTest.scala b/server-test/src/test/scala/testpkg/ClientTest.scala index 37ba93524..dcda788a7 100644 --- a/server-test/src/test/scala/testpkg/ClientTest.scala +++ b/server-test/src/test/scala/testpkg/ClientTest.scala @@ -13,7 +13,7 @@ import sbt.internal.client.NetworkClient import sbt.internal.util.Util import scala.collection.mutable -object ClientTest extends AbstractServerTest { +class ClientTest extends AbstractServerTest { override val testDirectory: String = "client" object NullInputStream extends InputStream { override def read(): Int = { @@ -88,28 +88,28 @@ object ClientTest extends AbstractServerTest { ) cps.lines } - test("exit success") { c => + test("exit success") { assert(client("willSucceed") == 0) } - test("exit failure") { _ => + test("exit failure") { assert(client("willFail") == 1) } - test("two commands") { _ => + test("two commands") { assert(client("compile;willSucceed") == 0) } - test("two commands with failing second") { _ => + test("two commands with failing second") { assert(client("compile;willFail") == 1) } - test("two commands with leading failure") { _ => + test("two commands with leading failure") { assert(client("willFail;willSucceed") == 1) } - test("three commands") { _ => + test("three commands") { assert(client("compile;clean;willSucceed") == 0) } - test("three commands with middle failure") { _ => + test("three commands with middle failure") { assert(client("compile;willFail;willSucceed") == 1) } - test("compi completions") { _ => + test("compi completions") { val expected = Vector( "compile", "compile:", @@ -131,7 +131,7 @@ object ClientTest extends AbstractServerTest { assert(complete("compi").toVector == expected) } - test("testOnly completions") { _ => + test("testOnly completions") { val testOnlyExpected = Vector( "testOnly", "testOnly/", @@ -143,7 +143,7 @@ object ClientTest extends AbstractServerTest { val testOnlyOptionsExpected = Vector("--", ";", "test.pkg.FooSpec") assert(complete("testOnly ") == testOnlyOptionsExpected) } - test("quote with semi") { _ => + test("quote with semi") { assert(complete("\"compile; fooB") == Vector("compile; fooBar")) } } diff --git a/server-test/src/test/scala/testpkg/EventsTest.scala b/server-test/src/test/scala/testpkg/EventsTest.scala index 9807c71f9..1ee0d6d4b 100644 --- a/server-test/src/test/scala/testpkg/EventsTest.scala +++ b/server-test/src/test/scala/testpkg/EventsTest.scala @@ -11,11 +11,11 @@ import scala.concurrent.duration._ import java.util.concurrent.atomic.AtomicInteger // starts svr using server-test/events and perform event related tests -object EventsTest extends AbstractServerTest { +class EventsTest extends AbstractServerTest { override val testDirectory: String = "events" val currentID = new AtomicInteger(1000) - test("report task failures in case of exceptions") { _ => + test("report task failures in case of exceptions") { val id = currentID.getAndIncrement() svr.sendJsonRpc( s"""{ "jsonrpc": "2.0", "id": $id, "method": "sbt/exec", "params": { "commandLine": "hello" } }""" @@ -25,7 +25,7 @@ object EventsTest extends AbstractServerTest { }) } - test("return error if cancelling non-matched task id") { _ => + test("return error if cancelling non-matched task id") { val id = currentID.getAndIncrement() svr.sendJsonRpc( s"""{ "jsonrpc": "2.0", "id":$id, "method": "sbt/exec", "params": { "commandLine": "run" } }""" diff --git a/server-test/src/test/scala/testpkg/HandshakeTest.scala b/server-test/src/test/scala/testpkg/HandshakeTest.scala index da5fea172..fa726a28e 100644 --- a/server-test/src/test/scala/testpkg/HandshakeTest.scala +++ b/server-test/src/test/scala/testpkg/HandshakeTest.scala @@ -10,10 +10,10 @@ package testpkg import scala.concurrent.duration._ // starts svr using server-test/handshake and perform basic tests -object HandshakeTest extends AbstractServerTest { +class HandshakeTest extends AbstractServerTest { override val testDirectory: String = "handshake" - test("handshake") { _ => + test("handshake") { svr.sendJsonRpc( """{ "jsonrpc": "2.0", "id": "3", "method": "sbt/setting", "params": { "setting": "root/name" } }""" ) @@ -22,7 +22,7 @@ object HandshakeTest extends AbstractServerTest { }) } - test("return number id when number id is sent") { _ => + test("return number id when number id is sent") { svr.sendJsonRpc( """{ "jsonrpc": "2.0", "id": 3, "method": "sbt/setting", "params": { "setting": "root/name" } }""" ) diff --git a/server-test/src/test/scala/testpkg/ResponseTest.scala b/server-test/src/test/scala/testpkg/ResponseTest.scala index 23d0f2e16..82b693b3b 100644 --- a/server-test/src/test/scala/testpkg/ResponseTest.scala +++ b/server-test/src/test/scala/testpkg/ResponseTest.scala @@ -10,32 +10,30 @@ package testpkg import scala.concurrent.duration._ // starts svr using server-test/response and perform custom server tests -object ResponseTest extends AbstractServerTest { +class ResponseTest extends AbstractServerTest { override val testDirectory: String = "response" - test("response from a command") { _ => - svr.sendJsonRpc( - """{ "jsonrpc": "2.0", "id": "10", "method": "foo/export", "params": {} }""" - ) + test("response from a command") { + svr.sendJsonRpc("""{ "jsonrpc": "2.0", "id": "10", "method": "foo/export", "params": {} }""") assert(svr.waitForString(10.seconds) { s => if (!s.contains("systemOut")) println(s) (s contains """"id":"10"""") && - (s contains "scala-library.jar") + (s contains "scala-library-2.12.17.jar") }) } - test("response from a task") { _ => + test("response from a task") { svr.sendJsonRpc( """{ "jsonrpc": "2.0", "id": "11", "method": "foo/rootClasspath", "params": {} }""" ) assert(svr.waitForString(10.seconds) { s => if (!s.contains("systemOut")) println(s) (s contains """"id":"11"""") && - (s contains "scala-library.jar") + (s contains "scala-library-2.12.17.jar") }) } - test("a command failure") { _ => + test("a command failure") { svr.sendJsonRpc( """{ "jsonrpc": "2.0", "id": "12", "method": "foo/fail", "params": {} }""" ) @@ -45,7 +43,7 @@ object ResponseTest extends AbstractServerTest { }) } - test("a command failure with custom code") { _ => + test("a command failure with custom code") { svr.sendJsonRpc( """{ "jsonrpc": "2.0", "id": "13", "method": "foo/customfail", "params": {} }""" ) @@ -55,7 +53,7 @@ object ResponseTest extends AbstractServerTest { }) } - test("a command with a notification") { _ => + test("a command with a notification") { svr.sendJsonRpc( """{ "jsonrpc": "2.0", "id": "14", "method": "foo/notification", "params": {} }""" ) @@ -65,7 +63,7 @@ object ResponseTest extends AbstractServerTest { }) } - test("respond concurrently from a task and the handler") { _ => + test("respond concurrently from a task and the handler") { svr.sendJsonRpc( """{ "jsonrpc": "2.0", "id": "15", "method": "foo/respondTwice", "params": {} }""" ) @@ -84,7 +82,7 @@ object ResponseTest extends AbstractServerTest { } } - test("concurrent result and error") { _ => + test("concurrent result and error") { svr.sendJsonRpc( """{ "jsonrpc": "2.0", "id": "16", "method": "foo/resultAndError", "params": {} }""" ) @@ -103,7 +101,7 @@ object ResponseTest extends AbstractServerTest { } } - test("response to a notification should not be sent") { _ => + test("response to a notification should not be sent") { svr.sendJsonRpc( """{ "jsonrpc": "2.0", "method": "foo/customNotification", "params": {} }""" ) diff --git a/server-test/src/test/scala/testpkg/ServerCompletionsTest.scala b/server-test/src/test/scala/testpkg/ServerCompletionsTest.scala index 30701f58d..2165de63d 100644 --- a/server-test/src/test/scala/testpkg/ServerCompletionsTest.scala +++ b/server-test/src/test/scala/testpkg/ServerCompletionsTest.scala @@ -10,21 +10,20 @@ package testpkg import scala.concurrent.duration._ // starts svr using server-test/completions and perform sbt/completion tests -object ServerCompletionsTest extends AbstractServerTest { +class ServerCompletionsTest extends AbstractServerTest { override val testDirectory: String = "completions" - test("return basic completions on request") { _ => + test("return basic completions on request") { val completionStr = """{ "query": "" }""" svr.sendJsonRpc( s"""{ "jsonrpc": "2.0", "id": 15, "method": "sbt/completion", "params": $completionStr }""" ) assert(svr.waitForString(10.seconds) { s => - println(s) s contains """"result":{"items":[""" }) } - test("return completion for custom tasks") { _ => + test("return completion for custom tasks") { val completionStr = """{ "query": "hell" }""" svr.sendJsonRpc( s"""{ "jsonrpc": "2.0", "id": 16, "method": "sbt/completion", "params": $completionStr }""" @@ -34,7 +33,7 @@ object ServerCompletionsTest extends AbstractServerTest { }) } - test("return completions for user classes") { _ => + test("return completions for user classes") { val completionStr = """{ "query": "testOnly org." }""" svr.sendJsonRpc( s"""{ "jsonrpc": "2.0", "id": 17, "method": "sbt/completion", "params": $completionStr }""" diff --git a/server-test/src/test/scala/testpkg/TestServer.scala b/server-test/src/test/scala/testpkg/TestServer.scala index 5a5027f30..6d5ca48b7 100644 --- a/server-test/src/test/scala/testpkg/TestServer.scala +++ b/server-test/src/test/scala/testpkg/TestServer.scala @@ -12,7 +12,6 @@ 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._ @@ -24,8 +23,10 @@ import scala.annotation.tailrec import scala.concurrent._ import scala.concurrent.duration._ import scala.util.{ Failure, Success, Try } +import org.scalatest.funsuite.AnyFunSuite +import org.scalatest.BeforeAndAfterAll -trait AbstractServerTest extends TestSuite[Unit] { +trait AbstractServerTest extends AnyFunSuite with BeforeAndAfterAll { private var temp: File = _ var svr: TestServer = _ def testDirectory: String @@ -38,7 +39,7 @@ trait AbstractServerTest extends TestSuite[Unit] { else p1 } - override def setupSuite(): Unit = { + override def beforeAll(): Unit = { val base = Files.createTempDirectory( Files.createDirectories(targetDir.toPath.resolve("test-server")), "server-test" @@ -47,15 +48,13 @@ trait AbstractServerTest extends TestSuite[Unit] { val classpath = TestProperties.classpath.split(File.pathSeparator).map(new File(_)) val sbtVersion = TestProperties.version val scalaVersion = TestProperties.scalaVersion - svr = TestServer.get(testDirectory, scalaVersion, sbtVersion, classpath, temp) + svr = TestServer.get(testDirectory, scalaVersion, sbtVersion, classpath.toSeq, temp) } - override def tearDownSuite(): Unit = { + override protected def afterAll(): Unit = { svr.bye() svr = null IO.delete(temp) } - override def setup(): Unit = () - override def tearDown(env: Unit): Unit = () } object TestServer { @@ -118,7 +117,7 @@ object TestServer { case _ => throw new IllegalStateException("No server scala version was specified.") } // Each test server instance will be executed in a Thread pool separated from the tests - val testServer = TestServer(baseDirectory, scalaVersion, sbtVersion, classpath) + val testServer = TestServer(baseDirectory, scalaVersion, sbtVersion, classpath.toSeq) // checking last log message after initialization // if something goes wrong here the communication streams are corrupted, restarting val init = @@ -164,7 +163,13 @@ case class TestServer( val forkOptions = ForkOptions() .withOutputStrategy(OutputStrategy.StdoutOutput) - .withRunJVMOptions(Vector("-Djline.terminal=none", "-Dsbt.io.virtual=false")) + .withRunJVMOptions( + Vector( + "-Djline.terminal=none", + "-Dsbt.io.virtual=false", + // "-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=1044" + ) + ) val process = RunFromSourceMain.fork(forkOptions, baseDirectory, scalaVersion, sbtVersion, classpath) @@ -174,6 +179,7 @@ case class TestServer( try IO.read(portfile).isEmpty catch { case _: IOException => true } def waitForPortfile(duration: FiniteDuration): Unit = { + hostLog(s"wait $duration until the server is ready to respond") val deadline = duration.fromNow var nextLog = 10.seconds.fromNow while (portfileIsEmpty() && !deadline.isOverdue && process.isAlive) { @@ -186,9 +192,7 @@ case class TestServer( if (deadline.isOverdue) sys.error(s"Timeout. $portfile is not found.") if (!process.isAlive) sys.error(s"Server unexpectedly terminated.") } - private val waitDuration: FiniteDuration = 1.minute - hostLog(s"wait $waitDuration until the server is ready to respond") - waitForPortfile(waitDuration) + waitForPortfile(1.minute) @tailrec private def connect(attempt: Int): Socket = { @@ -227,9 +231,7 @@ case class TestServer( s"""{ "jsonrpc": "2.0", "id": 1, "method": "initialize", "params": { "initializationOptions": { "skipAnalysis": true } } }""" ) - def test(f: TestServer => Future[Assertion]): Future[Assertion] = { - f(this) - } + def test(f: TestServer => Future[Unit]): Future[Unit] = f(this) def bye(): Unit = try { @@ -298,7 +300,7 @@ case class TestServer( } impl() } - final def waitFor[T: JsonReader](duration: FiniteDuration): T = { + final def waitFor[T: JsonReader](duration: FiniteDuration, debug: Boolean = false): T = { val deadline = duration.fromNow var lastEx: Throwable = null @tailrec def impl(): T = @@ -307,16 +309,17 @@ case class TestServer( if (lastEx != null) throw lastEx else throw new TimeoutException case s => + if debug then println(s) Parser .parseFromString(s) - .flatMap(jvalue => + .flatMap { jvalue => Converter.fromJson[T]( jvalue.toStandard .asInstanceOf[sjsonnew.shaded.scalajson.ast.JObject] .value("result") .toUnsafe ) - ) match { + } match { case Success(value) => value case Failure(exception) =>