diff --git a/bsp/worker/src/mill/bsp/worker/BspEvaluators.scala b/bsp/worker/src/mill/bsp/worker/BspEvaluators.scala index 1877ab5b203..7c7dc3d3c66 100644 --- a/bsp/worker/src/mill/bsp/worker/BspEvaluators.scala +++ b/bsp/worker/src/mill/bsp/worker/BspEvaluators.scala @@ -5,7 +5,7 @@ import mill.api.internal.{BaseModuleApi, BspModuleApi, EvaluatorApi, ModuleApi} private class BspEvaluators( workspaceDir: os.Path, - evaluators: Seq[EvaluatorApi], + val evaluators: Seq[EvaluatorApi], debug: (() => String) => Unit ) { diff --git a/bsp/worker/src/mill/bsp/worker/BspWorkerImpl.scala b/bsp/worker/src/mill/bsp/worker/BspWorkerImpl.scala index 5e97e80e04b..4c93d6d7cd5 100644 --- a/bsp/worker/src/mill/bsp/worker/BspWorkerImpl.scala +++ b/bsp/worker/src/mill/bsp/worker/BspWorkerImpl.scala @@ -7,10 +7,9 @@ import mill.bsp.Constants import mill.api.{Result, SystemStreams} import org.eclipse.lsp4j.jsonrpc.Launcher -import java.io.{PrintStream, PrintWriter} +import java.io.PrintWriter import java.util.concurrent.Executors -import scala.concurrent.duration.Duration -import scala.concurrent.{Await, CancellationException, ExecutionContext, Promise} +import scala.concurrent.{CancellationException, ExecutionContext, Future, Promise} object BspWorkerImpl { @@ -50,14 +49,19 @@ object BspWorkerImpl { lazy val listening = launcher.startListening() val bspServerHandle = new BspServerHandle { - override def runSession(evaluators: Seq[EvaluatorApi]): BspServerResult = { - millServer.updateEvaluator(Option(evaluators)) - millServer.sessionResult = None - while (millServer.sessionResult.isEmpty) Thread.sleep(1) - millServer.updateEvaluator(None) - val res = millServer.sessionResult.get - streams.err.println(s"Reload finished, result: $res") - res + override def startSession( + evaluators: Seq[EvaluatorApi], + errored: Boolean + ): Future[BspServerResult] = { + // FIXME We might be losing some shutdown requests here + val sessionResultPromise = Promise[BspServerResult]() + millServer.sessionResult = sessionResultPromise + millServer.updateEvaluator(Some(evaluators), errored = errored) + sessionResultPromise.future + } + + override def resetSession(): Unit = { + millServer.updateEvaluator(None, errored = false) } override def close(): Unit = { diff --git a/bsp/worker/src/mill/bsp/worker/MillBuildServer.scala b/bsp/worker/src/mill/bsp/worker/MillBuildServer.scala index 26dd359e5bd..bb47bb44e09 100644 --- a/bsp/worker/src/mill/bsp/worker/MillBuildServer.scala +++ b/bsp/worker/src/mill/bsp/worker/MillBuildServer.scala @@ -45,21 +45,98 @@ private class MillBuildServer( // progresses through: // // Set when the client connects - protected var client: BuildClient = scala.compiletime.uninitialized + protected var client: BuildClient = null // Set when the `buildInitialize` message comes in protected var sessionInfo: SessionInfo = scala.compiletime.uninitialized // Set when the `MillBuildBootstrap` completes and the evaluators are available private var bspEvaluators: Promise[BspEvaluators] = Promise[BspEvaluators]() + private var savedPreviousEvaluators = Option.empty[BspEvaluators] // Set when a session is completed, either due to reload or shutdown - private[worker] var sessionResult: Option[BspServerResult] = None + private[worker] var sessionResult = Promise[BspServerResult]() def initialized = sessionInfo != null - def updateEvaluator(evaluatorsOpt: Option[Seq[EvaluatorApi]]): Unit = { + def updateEvaluator(evaluatorsOpt: Option[Seq[EvaluatorApi]], errored: Boolean): Unit = { debug(s"Updating Evaluator: $evaluatorsOpt") + val previousEvaluatorsOpt = bspEvaluators.future.value.flatMap(_.toOption) + .orElse(savedPreviousEvaluators) if (bspEvaluators.isCompleted) bspEvaluators = Promise[BspEvaluators]() // replace the promise - evaluatorsOpt.foreach { evaluators => - bspEvaluators.success(new BspEvaluators(topLevelProjectRoot, evaluators, s => debug(s()))) + evaluatorsOpt match { + case None => + savedPreviousEvaluators = previousEvaluatorsOpt + case Some(evaluators) => + val updatedEvaluators = + if (errored) + previousEvaluatorsOpt.map(_.evaluators) match { + case Some(previous) => + evaluators.headOption match { + case None => // ??? + previous + case Some(headEvaluator) => + val idx = previous.indexWhere(_.outPathJava == headEvaluator.outPathJava) + if (idx < 0) // ??? + evaluators + else + previous.take(idx) ++ evaluators + } + case None => evaluators + } + else + evaluators + val bspEvaluators0 = + new BspEvaluators(topLevelProjectRoot, updatedEvaluators, s => debug(s())) + bspEvaluators.success(bspEvaluators0) + if (client != null) { + val newTargetIds = bspEvaluators0.bspModulesIdList.map { + case (id, (_, ev)) => + id -> ev + } + val newTargetIdsMap = newTargetIds.toMap + + val previousTargetIds = previousEvaluatorsOpt.map(_.bspModulesIdList).getOrElse(Nil).map { + case (id, (_, ev)) => + id -> ev + } + + val deleted0 = previousTargetIds.filterNot { + case (id, _) => + newTargetIdsMap.contains(id) + } + val previousTargetIdsMap = previousTargetIds.toMap + val (modified0, created0) = newTargetIds.partition { + case (id, _) => + previousTargetIdsMap.contains(id) + } + + val deletedEvents = deleted0.map { + case (id, _) => + val event = new bsp4j.BuildTargetEvent(id) + event.setKind(bsp4j.BuildTargetEventKind.DELETED) + event + } + val createdEvents = created0.map { + case (id, _) => + val event = new bsp4j.BuildTargetEvent(id) + event.setKind(bsp4j.BuildTargetEventKind.CREATED) + event + } + val modifiedEvents = modified0 + .filter { + case (id, ev) => + !previousTargetIdsMap.get(id).contains(ev) + } + .map { + case (id, ev) => + val event = new bsp4j.BuildTargetEvent(id) + event.setKind(bsp4j.BuildTargetEventKind.CHANGED) + event + } + + val allEvents = deletedEvents ++ createdEvents ++ modifiedEvents + + if (allEvents.nonEmpty) + client.onBuildTargetDidChange(new bsp4j.DidChangeBuildTarget(allEvents.asJava)) + } } } @@ -81,7 +158,7 @@ private class MillBuildServer( val supportedLangs = Constants.languages.asJava val capabilities = new BuildServerCapabilities - capabilities.setBuildTargetChangedProvider(false) + capabilities.setBuildTargetChangedProvider(true) capabilities.setCanReload(canReload) capabilities.setCompileProvider(new CompileProvider(supportedLangs)) capabilities.setDebugProvider(new DebugProvider(Seq().asJava)) @@ -139,7 +216,7 @@ private class MillBuildServer( override def onBuildExit(): Unit = { print("Entered onBuildExit") SemanticDbJavaModuleApi.resetContext() - sessionResult = Some(BspServerResult.Shutdown) + sessionResult.trySuccess(BspServerResult.Shutdown) onShutdown() } @@ -192,7 +269,7 @@ private class MillBuildServer( handlerRaw(checkInitialized = false) { // Instead stop and restart the command // BSP.install(evaluator) - sessionResult = Some(BspServerResult.ReloadWorkspace) + sessionResult.trySuccess(BspServerResult.ReloadWorkspace) ().asInstanceOf[Object] } diff --git a/core/api/src/mill/api/internal/BspContextApi.scala b/core/api/src/mill/api/internal/BspContextApi.scala deleted file mode 100644 index 9612f71761b..00000000000 --- a/core/api/src/mill/api/internal/BspContextApi.scala +++ /dev/null @@ -1,5 +0,0 @@ -package mill.api.internal - -private[mill] object BspContextApi { - @volatile var bspServerHandle: BspServerHandle = null -} diff --git a/core/api/src/mill/api/internal/BspServerHandle.scala b/core/api/src/mill/api/internal/BspServerHandle.scala index fe98bd908f6..0bb9363fdb0 100644 --- a/core/api/src/mill/api/internal/BspServerHandle.scala +++ b/core/api/src/mill/api/internal/BspServerHandle.scala @@ -1,13 +1,16 @@ package mill.api.internal +import scala.concurrent.Future + /** With this server handle you can interact with a running Mill BSP server. */ trait BspServerHandle { /** - * Runs a new session with the given evaluator. This one blocks until the session ends. - * @return The reason which the session ended, possibly indicating the wish for restart (e.g. in case of workspace reload). + * Starts a new session with the given evaluator. Doesn't block or wait for the session to end. */ - def runSession(evaluators: Seq[EvaluatorApi]): BspServerResult + def startSession(evaluators: Seq[EvaluatorApi], errored: Boolean): Future[BspServerResult] + + def resetSession(): Unit /** Stops the BSP server. */ def close(): Unit diff --git a/core/api/src/mill/api/internal/BspServerResult.scala b/core/api/src/mill/api/internal/BspServerResult.scala index 9a988645740..0d4dff0ac2f 100644 --- a/core/api/src/mill/api/internal/BspServerResult.scala +++ b/core/api/src/mill/api/internal/BspServerResult.scala @@ -9,7 +9,4 @@ object BspServerResult { /** The session or the server ended successfully. */ object Shutdown extends BspServerResult - - /** The session or the server ended with a failure. */ - object Failure extends BspServerResult } diff --git a/integration/ide/bsp-server/src/BspServerTestUtil.scala b/integration/bsp-util/src/BspServerTestUtil.scala similarity index 70% rename from integration/ide/bsp-server/src/BspServerTestUtil.scala rename to integration/bsp-util/src/BspServerTestUtil.scala index a187b8f5410..c624eeebfa5 100644 --- a/integration/ide/bsp-server/src/BspServerTestUtil.scala +++ b/integration/bsp-util/src/BspServerTestUtil.scala @@ -110,21 +110,36 @@ object BspServerTestUtil { def withBspServer[T]( workspacePath: os.Path, - millTestSuiteEnv: Map[String, String] + millTestSuiteEnv: Map[String, String], + client: b.BuildClient = DummyBuildClient, + millExecutableNoBspFile: Option[os.Path] = None )(f: (MillBuildServer, b.InitializeBuildResult) => T): T = { - val bspMetadataFile = workspacePath / Constants.bspDir / s"${Constants.serverName}.json" - assert(os.exists(bspMetadataFile)) - val contents = os.read(bspMetadataFile) - assert( - !contents.contains("--debug"), - contents.contains(s""""bspVersion":"$bsp4jVersion"""") - ) - val outputOnErrorOnly = System.getenv("CI") != null - val contentsJson = ujson.read(contents) - val bspCommand = contentsJson("argv").arr.map(_.str) + val bspCommand = millExecutableNoBspFile match { + case None => + val bspMetadataFile = workspacePath / Constants.bspDir / s"${Constants.serverName}.json" + assert(os.exists(bspMetadataFile)) + val contents = os.read(bspMetadataFile) + assert( + !contents.contains("--debug"), + contents.contains(s""""bspVersion":"$bsp4jVersion"""") + ) + val contentsJson = ujson.read(contents) + contentsJson("argv").arr.map(_.str) + case Some(millExecutableNoBspFile0) => + Seq( + millExecutableNoBspFile0.toString, + "--bsp", + "--disable-ticker", + "--color", + "false", + "--jobs", + "1" + ) + } + val stderr = new ByteArrayOutputStream val proc = os.proc(bspCommand).spawn( cwd = workspacePath, @@ -137,8 +152,6 @@ object BspServerTestUtil { env = millTestSuiteEnv ) - val client: b.BuildClient = DummyBuildClient - var success = false try { val launcher = new l.jsonrpc.Launcher.Builder[MillBuildServer] @@ -211,4 +224,64 @@ object BspServerTestUtil { coursierCache.toString -> "/coursier-cache", millWorkspace.toString -> "/mill-workspace" ) + + def scalaVersionNormalizedValues(): Seq[(String, String)] = { + val scala2Version = sys.props.getOrElse("TEST_SCALA_2_13_VERSION", ???) + val scala3Version = sys.props.getOrElse("MILL_SCALA_3_NEXT_VERSION", ???) + val scala2TransitiveSubstitutions = transitiveDependenciesSubstitutions( + coursierapi.Dependency.of( + "org.scala-lang", + "scala-compiler", + scala2Version + ), + _.getModule.getOrganization != "org.scala-lang" + ) + val scala3TransitiveSubstitutions = transitiveDependenciesSubstitutions( + coursierapi.Dependency.of( + "org.scala-lang", + "scala3-compiler_3", + scala3Version + ), + _.getModule.getOrganization != "org.scala-lang" + ) + + scala2TransitiveSubstitutions ++ scala3TransitiveSubstitutions ++ + Seq( + scala2Version -> "", + scala3Version -> "" + ) + } + + def kotlinVersionNormalizedValues(): Seq[(String, String)] = { + val kotlinVersion = sys.props.getOrElse("TEST_KOTLIN_VERSION", ???) + val kotlinTransitiveSubstitutions = transitiveDependenciesSubstitutions( + coursierapi.Dependency.of( + "org.jetbrains.kotlin", + "kotlin-stdlib", + kotlinVersion + ), + _.getModule.getOrganization != "org.jetbrains.kotlin" + ) + kotlinTransitiveSubstitutions ++ Seq(kotlinVersion -> "") + } + + def transitiveDependenciesSubstitutions( + dependency: coursierapi.Dependency, + filter: coursierapi.Dependency => Boolean + ): Seq[(String, String)] = { + val fetchRes = coursierapi.Fetch.create() + .addDependencies(dependency) + .fetchResult() + fetchRes.getDependencies.asScala + .filter(filter) + .map { dep => + val organization = dep.getModule.getOrganization + val name = dep.getModule.getName + val prefix = (organization.split('.') :+ name).mkString("/") + def basePath(version: String): String = + s"$prefix/$version/$name-$version" + basePath(dep.getVersion) -> basePath(s"<$name-version>") + } + .toSeq + } } diff --git a/integration/ide/bsp-server-reload/resources/project/app/src/milltests/TheApp.scala b/integration/ide/bsp-server-reload/resources/project/app/src/milltests/TheApp.scala new file mode 100644 index 00000000000..054960d3017 --- /dev/null +++ b/integration/ide/bsp-server-reload/resources/project/app/src/milltests/TheApp.scala @@ -0,0 +1,6 @@ +package milltests + +object TheApp { + def main(args: Array[String]): Unit = + println(Lib.message) +} diff --git a/integration/ide/bsp-server-reload/resources/project/build.mill.base b/integration/ide/bsp-server-reload/resources/project/build.mill.base new file mode 100644 index 00000000000..3edae7b7de7 --- /dev/null +++ b/integration/ide/bsp-server-reload/resources/project/build.mill.base @@ -0,0 +1,25 @@ +package build + +import mill.scalalib._ + +object thing extends ScalaModule { + def scalaVersion = Option(System.getenv("TEST_SCALA_2_13_VERSION")).getOrElse(???) + def mvnDeps = Seq( + mvn"com.lihaoyi::pprint:0.9.0" + ) +} + +object lib extends ScalaModule { + def scalaVersion = Option(System.getenv("TEST_SCALA_2_13_VERSION")).getOrElse(???) + def mvnDeps = Seq( + mvn"org.slf4j:slf4j-api:2.0.16" + ) +} + +object app extends ScalaModule { + def scalaVersion = Option(System.getenv("TEST_SCALA_2_13_VERSION")).getOrElse(???) + def moduleDeps = Seq(lib) + def mvnDeps = Seq( + mvn"ch.qos.logback:logback-core:1.5.15" + ) +} diff --git a/integration/ide/bsp-server-reload/resources/project/build.mill.broken b/integration/ide/bsp-server-reload/resources/project/build.mill.broken new file mode 100644 index 00000000000..be72d21371d --- /dev/null +++ b/integration/ide/bsp-server-reload/resources/project/build.mill.broken @@ -0,0 +1,25 @@ +package build + +import mill.scalalib._ + +object thing extends ScalaModule { + def scalaVersion = Option(System.getenv("TEST_SCALA_2_13_VERSION")).getOrElse(???) + def mvnDeps = Seq( + mvn"com.lihaoyi::pprint:0.9.0" + ) +} + +object lib extends ScalaModule { + def scalaVersion = Option(System.getenv("TEST_SCALA_2_13_VERSION")).getOrElse(???) + def mvnDeps = Seq( + mvnz"org.slf4j:slf4j-api:2.0.16" + ) +} + +object app extends ScalaModule { + def scalaVersion = Option(System.getenv("TEST_SCALA_2_13_VERSION")).getOrElse(???) + def moduleDeps = Seq(lib) + def mvnDeps = Seq( + mvn"ch.qos.logback:logback-core:1.5.15" + ) +} diff --git a/integration/ide/bsp-server-reload/resources/project/build.mill.deletion-and-renaming b/integration/ide/bsp-server-reload/resources/project/build.mill.deletion-and-renaming new file mode 100644 index 00000000000..3fc2b75e34e --- /dev/null +++ b/integration/ide/bsp-server-reload/resources/project/build.mill.deletion-and-renaming @@ -0,0 +1,18 @@ +package build + +import mill.scalalib._ + +object lib extends ScalaModule { + def scalaVersion = Option(System.getenv("TEST_SCALA_2_13_VERSION")).getOrElse(???) + def mvnDeps = Seq( + mvn"org.slf4j:slf4j-api:2.0.16" + ) +} + +object `my-app` extends ScalaModule { + def scalaVersion = Option(System.getenv("TEST_SCALA_2_13_VERSION")).getOrElse(???) + def moduleDeps = Seq(lib) + def mvnDeps = Seq( + mvn"ch.qos.logback:logback-core:1.5.15" + ) +} diff --git a/integration/ide/bsp-server-reload/resources/project/lib/src/milltests/Lib.scala b/integration/ide/bsp-server-reload/resources/project/lib/src/milltests/Lib.scala new file mode 100644 index 00000000000..10a35981b81 --- /dev/null +++ b/integration/ide/bsp-server-reload/resources/project/lib/src/milltests/Lib.scala @@ -0,0 +1,5 @@ +package milltests + +object Lib { + def message = "Hello" +} diff --git a/integration/ide/bsp-server-reload/resources/snapshots/broken/back/workspace-build-targets.json b/integration/ide/bsp-server-reload/resources/snapshots/broken/back/workspace-build-targets.json new file mode 100644 index 00000000000..987c16e834e --- /dev/null +++ b/integration/ide/bsp-server-reload/resources/snapshots/broken/back/workspace-build-targets.json @@ -0,0 +1,197 @@ +{ + "targets": [ + { + "id": { + "uri": "file:///workspace/app" + }, + "displayName": "app", + "baseDirectory": "file:///workspace/app", + "tags": [ + "library", + "application" + ], + "languageIds": [ + "java", + "scala" + ], + "dependencies": [ + { + "uri": "file:///workspace/lib" + } + ], + "capabilities": { + "canCompile": true, + "canTest": false, + "canRun": true, + "canDebug": false + }, + "dataKind": "scala", + "data": { + "scalaOrganization": "org.scala-lang", + "scalaVersion": "", + "scalaBinaryVersion": "2.13", + "platform": 1, + "jars": [ + "file:///coursier-cache/https/repo1.maven.org/maven2/org/scala-lang/scala-compiler//scala-compiler-.jar", + "file:///coursier-cache/https/repo1.maven.org/maven2/org/scala-lang/scala-reflect//scala-reflect-.jar", + "file:///coursier-cache/https/repo1.maven.org/maven2/org/scala-lang/scala-library//scala-library-.jar", + "file:///coursier-cache/https/repo1.maven.org/maven2/io/github/java-diff-utils/java-diff-utils//java-diff-utils-.jar", + "file:///coursier-cache/https/repo1.maven.org/maven2/org/jline/jline//jline-.jar", + "file:///coursier-cache/https/repo1.maven.org/maven2/net/java/dev/jna/jna//jna-.jar" + ], + "jvmBuildTarget": { + "javaHome": "java-home", + "javaVersion": "" + } + } + }, + { + "id": { + "uri": "file:///workspace/lib" + }, + "displayName": "lib", + "baseDirectory": "file:///workspace/lib", + "tags": [ + "library", + "application" + ], + "languageIds": [ + "java", + "scala" + ], + "dependencies": [], + "capabilities": { + "canCompile": true, + "canTest": false, + "canRun": true, + "canDebug": false + }, + "dataKind": "scala", + "data": { + "scalaOrganization": "org.scala-lang", + "scalaVersion": "", + "scalaBinaryVersion": "2.13", + "platform": 1, + "jars": [ + "file:///coursier-cache/https/repo1.maven.org/maven2/org/scala-lang/scala-compiler//scala-compiler-.jar", + "file:///coursier-cache/https/repo1.maven.org/maven2/org/scala-lang/scala-reflect//scala-reflect-.jar", + "file:///coursier-cache/https/repo1.maven.org/maven2/org/scala-lang/scala-library//scala-library-.jar", + "file:///coursier-cache/https/repo1.maven.org/maven2/io/github/java-diff-utils/java-diff-utils//java-diff-utils-.jar", + "file:///coursier-cache/https/repo1.maven.org/maven2/org/jline/jline//jline-.jar", + "file:///coursier-cache/https/repo1.maven.org/maven2/net/java/dev/jna/jna//jna-.jar" + ], + "jvmBuildTarget": { + "javaHome": "java-home", + "javaVersion": "" + } + } + }, + { + "id": { + "uri": "file:///workspace/mill-build" + }, + "displayName": "mill-build/", + "baseDirectory": "file:///workspace/mill-build", + "tags": [ + "library", + "application" + ], + "languageIds": [ + "java", + "scala" + ], + "dependencies": [], + "capabilities": { + "canCompile": true, + "canTest": false, + "canRun": true, + "canDebug": false + }, + "dataKind": "scala", + "data": { + "scalaOrganization": "org.scala-lang", + "scalaVersion": "", + "scalaBinaryVersion": "3", + "platform": 1, + "jars": [ + "file:///coursier-cache/https/repo1.maven.org/maven2/org/scala-lang/scala3-compiler_3//scala3-compiler_3-.jar", + "file:///coursier-cache/https/repo1.maven.org/maven2/org/scala-lang/scala3-library_3//scala3-library_3-.jar", + "file:///coursier-cache/https/repo1.maven.org/maven2/org/scala-lang/scala3-interfaces//scala3-interfaces-.jar", + "file:///coursier-cache/https/repo1.maven.org/maven2/org/scala-lang/tasty-core_3//tasty-core_3-.jar", + "file:///coursier-cache/https/repo1.maven.org/maven2/org/scala-lang/modules/scala-asm//scala-asm-.jar", + "file:///coursier-cache/https/repo1.maven.org/maven2/org/scala-sbt/compiler-interface//compiler-interface-.jar", + "file:///coursier-cache/https/repo1.maven.org/maven2/org/jline/jline-reader//jline-reader-.jar", + "file:///coursier-cache/https/repo1.maven.org/maven2/org/jline/jline-terminal//jline-terminal-.jar", + "file:///coursier-cache/https/repo1.maven.org/maven2/org/jline/jline-terminal-jna//jline-terminal-jna-.jar", + "file:///coursier-cache/https/repo1.maven.org/maven2/org/scala-lang/scala-library//scala-library-.jar", + "file:///coursier-cache/https/repo1.maven.org/maven2/org/scala-sbt/util-interface//util-interface-.jar", + "file:///coursier-cache/https/repo1.maven.org/maven2/org/jline/jline-native//jline-native-.jar", + "file:///coursier-cache/https/repo1.maven.org/maven2/net/java/dev/jna/jna//jna-.jar" + ], + "jvmBuildTarget": { + "javaHome": "java-home", + "javaVersion": "" + } + } + }, + { + "id": { + "uri": "file:///workspace/mill-synthetic-root-target" + }, + "displayName": "mill-synthetic-root", + "baseDirectory": "file:///workspace", + "tags": [ + "manual" + ], + "languageIds": [], + "dependencies": [], + "capabilities": { + "canCompile": false, + "canTest": false, + "canRun": false, + "canDebug": false + } + }, + { + "id": { + "uri": "file:///workspace/thing" + }, + "displayName": "thing", + "baseDirectory": "file:///workspace/thing", + "tags": [ + "library", + "application" + ], + "languageIds": [ + "java", + "scala" + ], + "dependencies": [], + "capabilities": { + "canCompile": true, + "canTest": false, + "canRun": true, + "canDebug": false + }, + "dataKind": "scala", + "data": { + "scalaOrganization": "org.scala-lang", + "scalaVersion": "", + "scalaBinaryVersion": "2.13", + "platform": 1, + "jars": [ + "file:///coursier-cache/https/repo1.maven.org/maven2/org/scala-lang/scala-compiler//scala-compiler-.jar", + "file:///coursier-cache/https/repo1.maven.org/maven2/org/scala-lang/scala-reflect//scala-reflect-.jar", + "file:///coursier-cache/https/repo1.maven.org/maven2/org/scala-lang/scala-library//scala-library-.jar", + "file:///coursier-cache/https/repo1.maven.org/maven2/io/github/java-diff-utils/java-diff-utils//java-diff-utils-.jar", + "file:///coursier-cache/https/repo1.maven.org/maven2/org/jline/jline//jline-.jar", + "file:///coursier-cache/https/repo1.maven.org/maven2/net/java/dev/jna/jna//jna-.jar" + ], + "jvmBuildTarget": { + "javaHome": "java-home", + "javaVersion": "" + } + } + } + ] +} \ No newline at end of file diff --git a/integration/ide/bsp-server-reload/resources/snapshots/broken/changed/workspace-build-targets.json b/integration/ide/bsp-server-reload/resources/snapshots/broken/changed/workspace-build-targets.json new file mode 100644 index 00000000000..987c16e834e --- /dev/null +++ b/integration/ide/bsp-server-reload/resources/snapshots/broken/changed/workspace-build-targets.json @@ -0,0 +1,197 @@ +{ + "targets": [ + { + "id": { + "uri": "file:///workspace/app" + }, + "displayName": "app", + "baseDirectory": "file:///workspace/app", + "tags": [ + "library", + "application" + ], + "languageIds": [ + "java", + "scala" + ], + "dependencies": [ + { + "uri": "file:///workspace/lib" + } + ], + "capabilities": { + "canCompile": true, + "canTest": false, + "canRun": true, + "canDebug": false + }, + "dataKind": "scala", + "data": { + "scalaOrganization": "org.scala-lang", + "scalaVersion": "", + "scalaBinaryVersion": "2.13", + "platform": 1, + "jars": [ + "file:///coursier-cache/https/repo1.maven.org/maven2/org/scala-lang/scala-compiler//scala-compiler-.jar", + "file:///coursier-cache/https/repo1.maven.org/maven2/org/scala-lang/scala-reflect//scala-reflect-.jar", + "file:///coursier-cache/https/repo1.maven.org/maven2/org/scala-lang/scala-library//scala-library-.jar", + "file:///coursier-cache/https/repo1.maven.org/maven2/io/github/java-diff-utils/java-diff-utils//java-diff-utils-.jar", + "file:///coursier-cache/https/repo1.maven.org/maven2/org/jline/jline//jline-.jar", + "file:///coursier-cache/https/repo1.maven.org/maven2/net/java/dev/jna/jna//jna-.jar" + ], + "jvmBuildTarget": { + "javaHome": "java-home", + "javaVersion": "" + } + } + }, + { + "id": { + "uri": "file:///workspace/lib" + }, + "displayName": "lib", + "baseDirectory": "file:///workspace/lib", + "tags": [ + "library", + "application" + ], + "languageIds": [ + "java", + "scala" + ], + "dependencies": [], + "capabilities": { + "canCompile": true, + "canTest": false, + "canRun": true, + "canDebug": false + }, + "dataKind": "scala", + "data": { + "scalaOrganization": "org.scala-lang", + "scalaVersion": "", + "scalaBinaryVersion": "2.13", + "platform": 1, + "jars": [ + "file:///coursier-cache/https/repo1.maven.org/maven2/org/scala-lang/scala-compiler//scala-compiler-.jar", + "file:///coursier-cache/https/repo1.maven.org/maven2/org/scala-lang/scala-reflect//scala-reflect-.jar", + "file:///coursier-cache/https/repo1.maven.org/maven2/org/scala-lang/scala-library//scala-library-.jar", + "file:///coursier-cache/https/repo1.maven.org/maven2/io/github/java-diff-utils/java-diff-utils//java-diff-utils-.jar", + "file:///coursier-cache/https/repo1.maven.org/maven2/org/jline/jline//jline-.jar", + "file:///coursier-cache/https/repo1.maven.org/maven2/net/java/dev/jna/jna//jna-.jar" + ], + "jvmBuildTarget": { + "javaHome": "java-home", + "javaVersion": "" + } + } + }, + { + "id": { + "uri": "file:///workspace/mill-build" + }, + "displayName": "mill-build/", + "baseDirectory": "file:///workspace/mill-build", + "tags": [ + "library", + "application" + ], + "languageIds": [ + "java", + "scala" + ], + "dependencies": [], + "capabilities": { + "canCompile": true, + "canTest": false, + "canRun": true, + "canDebug": false + }, + "dataKind": "scala", + "data": { + "scalaOrganization": "org.scala-lang", + "scalaVersion": "", + "scalaBinaryVersion": "3", + "platform": 1, + "jars": [ + "file:///coursier-cache/https/repo1.maven.org/maven2/org/scala-lang/scala3-compiler_3//scala3-compiler_3-.jar", + "file:///coursier-cache/https/repo1.maven.org/maven2/org/scala-lang/scala3-library_3//scala3-library_3-.jar", + "file:///coursier-cache/https/repo1.maven.org/maven2/org/scala-lang/scala3-interfaces//scala3-interfaces-.jar", + "file:///coursier-cache/https/repo1.maven.org/maven2/org/scala-lang/tasty-core_3//tasty-core_3-.jar", + "file:///coursier-cache/https/repo1.maven.org/maven2/org/scala-lang/modules/scala-asm//scala-asm-.jar", + "file:///coursier-cache/https/repo1.maven.org/maven2/org/scala-sbt/compiler-interface//compiler-interface-.jar", + "file:///coursier-cache/https/repo1.maven.org/maven2/org/jline/jline-reader//jline-reader-.jar", + "file:///coursier-cache/https/repo1.maven.org/maven2/org/jline/jline-terminal//jline-terminal-.jar", + "file:///coursier-cache/https/repo1.maven.org/maven2/org/jline/jline-terminal-jna//jline-terminal-jna-.jar", + "file:///coursier-cache/https/repo1.maven.org/maven2/org/scala-lang/scala-library//scala-library-.jar", + "file:///coursier-cache/https/repo1.maven.org/maven2/org/scala-sbt/util-interface//util-interface-.jar", + "file:///coursier-cache/https/repo1.maven.org/maven2/org/jline/jline-native//jline-native-.jar", + "file:///coursier-cache/https/repo1.maven.org/maven2/net/java/dev/jna/jna//jna-.jar" + ], + "jvmBuildTarget": { + "javaHome": "java-home", + "javaVersion": "" + } + } + }, + { + "id": { + "uri": "file:///workspace/mill-synthetic-root-target" + }, + "displayName": "mill-synthetic-root", + "baseDirectory": "file:///workspace", + "tags": [ + "manual" + ], + "languageIds": [], + "dependencies": [], + "capabilities": { + "canCompile": false, + "canTest": false, + "canRun": false, + "canDebug": false + } + }, + { + "id": { + "uri": "file:///workspace/thing" + }, + "displayName": "thing", + "baseDirectory": "file:///workspace/thing", + "tags": [ + "library", + "application" + ], + "languageIds": [ + "java", + "scala" + ], + "dependencies": [], + "capabilities": { + "canCompile": true, + "canTest": false, + "canRun": true, + "canDebug": false + }, + "dataKind": "scala", + "data": { + "scalaOrganization": "org.scala-lang", + "scalaVersion": "", + "scalaBinaryVersion": "2.13", + "platform": 1, + "jars": [ + "file:///coursier-cache/https/repo1.maven.org/maven2/org/scala-lang/scala-compiler//scala-compiler-.jar", + "file:///coursier-cache/https/repo1.maven.org/maven2/org/scala-lang/scala-reflect//scala-reflect-.jar", + "file:///coursier-cache/https/repo1.maven.org/maven2/org/scala-lang/scala-library//scala-library-.jar", + "file:///coursier-cache/https/repo1.maven.org/maven2/io/github/java-diff-utils/java-diff-utils//java-diff-utils-.jar", + "file:///coursier-cache/https/repo1.maven.org/maven2/org/jline/jline//jline-.jar", + "file:///coursier-cache/https/repo1.maven.org/maven2/net/java/dev/jna/jna//jna-.jar" + ], + "jvmBuildTarget": { + "javaHome": "java-home", + "javaVersion": "" + } + } + } + ] +} \ No newline at end of file diff --git a/integration/ide/bsp-server-reload/resources/snapshots/broken/start/initialize-build-result.json b/integration/ide/bsp-server-reload/resources/snapshots/broken/start/initialize-build-result.json new file mode 100644 index 00000000000..dd49a77e8a9 --- /dev/null +++ b/integration/ide/bsp-server-reload/resources/snapshots/broken/start/initialize-build-result.json @@ -0,0 +1,41 @@ +{ + "displayName": "mill-bsp", + "version": "", + "bspVersion": "", + "capabilities": { + "compileProvider": { + "languageIds": [ + "java", + "scala", + "kotlin" + ] + }, + "testProvider": { + "languageIds": [ + "java", + "scala", + "kotlin" + ] + }, + "runProvider": { + "languageIds": [ + "java", + "scala", + "kotlin" + ] + }, + "debugProvider": { + "languageIds": [] + }, + "inverseSourcesProvider": true, + "dependencySourcesProvider": true, + "dependencyModulesProvider": true, + "resourcesProvider": true, + "outputPathsProvider": true, + "buildTargetChangedProvider": true, + "jvmRunEnvironmentProvider": true, + "jvmTestEnvironmentProvider": true, + "canReload": true, + "jvmCompileClasspathProvider": false + } +} \ No newline at end of file diff --git a/integration/ide/bsp-server-reload/resources/snapshots/broken/start/workspace-build-targets.json b/integration/ide/bsp-server-reload/resources/snapshots/broken/start/workspace-build-targets.json new file mode 100644 index 00000000000..55f15696eca --- /dev/null +++ b/integration/ide/bsp-server-reload/resources/snapshots/broken/start/workspace-build-targets.json @@ -0,0 +1,70 @@ +{ + "targets": [ + { + "id": { + "uri": "file:///workspace/mill-build" + }, + "displayName": "mill-build/", + "baseDirectory": "file:///workspace/mill-build", + "tags": [ + "library", + "application" + ], + "languageIds": [ + "java", + "scala" + ], + "dependencies": [], + "capabilities": { + "canCompile": true, + "canTest": false, + "canRun": true, + "canDebug": false + }, + "dataKind": "scala", + "data": { + "scalaOrganization": "org.scala-lang", + "scalaVersion": "", + "scalaBinaryVersion": "3", + "platform": 1, + "jars": [ + "file:///coursier-cache/https/repo1.maven.org/maven2/org/scala-lang/scala3-compiler_3//scala3-compiler_3-.jar", + "file:///coursier-cache/https/repo1.maven.org/maven2/org/scala-lang/scala3-library_3//scala3-library_3-.jar", + "file:///coursier-cache/https/repo1.maven.org/maven2/org/scala-lang/scala3-interfaces//scala3-interfaces-.jar", + "file:///coursier-cache/https/repo1.maven.org/maven2/org/scala-lang/tasty-core_3//tasty-core_3-.jar", + "file:///coursier-cache/https/repo1.maven.org/maven2/org/scala-lang/modules/scala-asm//scala-asm-.jar", + "file:///coursier-cache/https/repo1.maven.org/maven2/org/scala-sbt/compiler-interface//compiler-interface-.jar", + "file:///coursier-cache/https/repo1.maven.org/maven2/org/jline/jline-reader//jline-reader-.jar", + "file:///coursier-cache/https/repo1.maven.org/maven2/org/jline/jline-terminal//jline-terminal-.jar", + "file:///coursier-cache/https/repo1.maven.org/maven2/org/jline/jline-terminal-jna//jline-terminal-jna-.jar", + "file:///coursier-cache/https/repo1.maven.org/maven2/org/scala-lang/scala-library//scala-library-.jar", + "file:///coursier-cache/https/repo1.maven.org/maven2/org/scala-sbt/util-interface//util-interface-.jar", + "file:///coursier-cache/https/repo1.maven.org/maven2/org/jline/jline-native//jline-native-.jar", + "file:///coursier-cache/https/repo1.maven.org/maven2/net/java/dev/jna/jna//jna-.jar" + ], + "jvmBuildTarget": { + "javaHome": "java-home", + "javaVersion": "" + } + } + }, + { + "id": { + "uri": "file:///workspace/mill-synthetic-root-target" + }, + "displayName": "mill-synthetic-root", + "baseDirectory": "file:///workspace", + "tags": [ + "manual" + ], + "languageIds": [], + "dependencies": [], + "capabilities": { + "canCompile": false, + "canTest": false, + "canRun": false, + "canDebug": false + } + } + ] +} \ No newline at end of file diff --git a/integration/ide/bsp-server-reload/resources/snapshots/reload/changed/workspace-build-targets.json b/integration/ide/bsp-server-reload/resources/snapshots/reload/changed/workspace-build-targets.json new file mode 100644 index 00000000000..cd53c15806a --- /dev/null +++ b/integration/ide/bsp-server-reload/resources/snapshots/reload/changed/workspace-build-targets.json @@ -0,0 +1,156 @@ +{ + "targets": [ + { + "id": { + "uri": "file:///workspace/lib" + }, + "displayName": "lib", + "baseDirectory": "file:///workspace/lib", + "tags": [ + "library", + "application" + ], + "languageIds": [ + "java", + "scala" + ], + "dependencies": [], + "capabilities": { + "canCompile": true, + "canTest": false, + "canRun": true, + "canDebug": false + }, + "dataKind": "scala", + "data": { + "scalaOrganization": "org.scala-lang", + "scalaVersion": "", + "scalaBinaryVersion": "2.13", + "platform": 1, + "jars": [ + "file:///coursier-cache/https/repo1.maven.org/maven2/org/scala-lang/scala-compiler//scala-compiler-.jar", + "file:///coursier-cache/https/repo1.maven.org/maven2/org/scala-lang/scala-reflect//scala-reflect-.jar", + "file:///coursier-cache/https/repo1.maven.org/maven2/org/scala-lang/scala-library//scala-library-.jar", + "file:///coursier-cache/https/repo1.maven.org/maven2/io/github/java-diff-utils/java-diff-utils//java-diff-utils-.jar", + "file:///coursier-cache/https/repo1.maven.org/maven2/org/jline/jline//jline-.jar", + "file:///coursier-cache/https/repo1.maven.org/maven2/net/java/dev/jna/jna//jna-.jar" + ], + "jvmBuildTarget": { + "javaHome": "java-home", + "javaVersion": "" + } + } + }, + { + "id": { + "uri": "file:///workspace/mill-build" + }, + "displayName": "mill-build/", + "baseDirectory": "file:///workspace/mill-build", + "tags": [ + "library", + "application" + ], + "languageIds": [ + "java", + "scala" + ], + "dependencies": [], + "capabilities": { + "canCompile": true, + "canTest": false, + "canRun": true, + "canDebug": false + }, + "dataKind": "scala", + "data": { + "scalaOrganization": "org.scala-lang", + "scalaVersion": "", + "scalaBinaryVersion": "3", + "platform": 1, + "jars": [ + "file:///coursier-cache/https/repo1.maven.org/maven2/org/scala-lang/scala3-compiler_3//scala3-compiler_3-.jar", + "file:///coursier-cache/https/repo1.maven.org/maven2/org/scala-lang/scala3-library_3//scala3-library_3-.jar", + "file:///coursier-cache/https/repo1.maven.org/maven2/org/scala-lang/scala3-interfaces//scala3-interfaces-.jar", + "file:///coursier-cache/https/repo1.maven.org/maven2/org/scala-lang/tasty-core_3//tasty-core_3-.jar", + "file:///coursier-cache/https/repo1.maven.org/maven2/org/scala-lang/modules/scala-asm//scala-asm-.jar", + "file:///coursier-cache/https/repo1.maven.org/maven2/org/scala-sbt/compiler-interface//compiler-interface-.jar", + "file:///coursier-cache/https/repo1.maven.org/maven2/org/jline/jline-reader//jline-reader-.jar", + "file:///coursier-cache/https/repo1.maven.org/maven2/org/jline/jline-terminal//jline-terminal-.jar", + "file:///coursier-cache/https/repo1.maven.org/maven2/org/jline/jline-terminal-jna//jline-terminal-jna-.jar", + "file:///coursier-cache/https/repo1.maven.org/maven2/org/scala-lang/scala-library//scala-library-.jar", + "file:///coursier-cache/https/repo1.maven.org/maven2/org/scala-sbt/util-interface//util-interface-.jar", + "file:///coursier-cache/https/repo1.maven.org/maven2/org/jline/jline-native//jline-native-.jar", + "file:///coursier-cache/https/repo1.maven.org/maven2/net/java/dev/jna/jna//jna-.jar" + ], + "jvmBuildTarget": { + "javaHome": "java-home", + "javaVersion": "" + } + } + }, + { + "id": { + "uri": "file:///workspace/mill-synthetic-root-target" + }, + "displayName": "mill-synthetic-root", + "baseDirectory": "file:///workspace", + "tags": [ + "manual" + ], + "languageIds": [], + "dependencies": [], + "capabilities": { + "canCompile": false, + "canTest": false, + "canRun": false, + "canDebug": false + } + }, + { + "id": { + "uri": "file:///workspace/my-app" + }, + "displayName": "my-app", + "baseDirectory": "file:///workspace/my-app", + "tags": [ + "library", + "application" + ], + "languageIds": [ + "java", + "scala" + ], + "dependencies": [ + { + "uri": "file:///workspace/lib" + } + ], + "capabilities": { + "canCompile": true, + "canTest": false, + "canRun": true, + "canDebug": false + }, + "dataKind": "scala", + "data": { + "scalaOrganization": "org.scala-lang", + "scalaVersion": "", + "scalaBinaryVersion": "2.13", + "platform": 1, + "jars": [ + "file:///coursier-cache/https/repo1.maven.org/maven2/org/scala-lang/scala-compiler//scala-compiler-.jar", + "file:///coursier-cache/https/repo1.maven.org/maven2/org/scala-lang/scala-reflect//scala-reflect-.jar", + "file:///coursier-cache/https/repo1.maven.org/maven2/org/scala-lang/scala-library//scala-library-.jar", + "file:///coursier-cache/https/repo1.maven.org/maven2/io/github/java-diff-utils/java-diff-utils//java-diff-utils-.jar", + "file:///coursier-cache/https/repo1.maven.org/maven2/org/jline/jline//jline-.jar", + "file:///coursier-cache/https/repo1.maven.org/maven2/net/java/dev/jna/jna//jna-.jar" + ], + "jvmBuildTarget": { + "javaHome": "java-home", + "javaVersion": "" + } + } + } + ] +} \ No newline at end of file diff --git a/integration/ide/bsp-server-reload/resources/snapshots/reload/start/initialize-build-result.json b/integration/ide/bsp-server-reload/resources/snapshots/reload/start/initialize-build-result.json new file mode 100644 index 00000000000..dd49a77e8a9 --- /dev/null +++ b/integration/ide/bsp-server-reload/resources/snapshots/reload/start/initialize-build-result.json @@ -0,0 +1,41 @@ +{ + "displayName": "mill-bsp", + "version": "", + "bspVersion": "", + "capabilities": { + "compileProvider": { + "languageIds": [ + "java", + "scala", + "kotlin" + ] + }, + "testProvider": { + "languageIds": [ + "java", + "scala", + "kotlin" + ] + }, + "runProvider": { + "languageIds": [ + "java", + "scala", + "kotlin" + ] + }, + "debugProvider": { + "languageIds": [] + }, + "inverseSourcesProvider": true, + "dependencySourcesProvider": true, + "dependencyModulesProvider": true, + "resourcesProvider": true, + "outputPathsProvider": true, + "buildTargetChangedProvider": true, + "jvmRunEnvironmentProvider": true, + "jvmTestEnvironmentProvider": true, + "canReload": true, + "jvmCompileClasspathProvider": false + } +} \ No newline at end of file diff --git a/integration/ide/bsp-server-reload/resources/snapshots/reload/start/workspace-build-targets.json b/integration/ide/bsp-server-reload/resources/snapshots/reload/start/workspace-build-targets.json new file mode 100644 index 00000000000..987c16e834e --- /dev/null +++ b/integration/ide/bsp-server-reload/resources/snapshots/reload/start/workspace-build-targets.json @@ -0,0 +1,197 @@ +{ + "targets": [ + { + "id": { + "uri": "file:///workspace/app" + }, + "displayName": "app", + "baseDirectory": "file:///workspace/app", + "tags": [ + "library", + "application" + ], + "languageIds": [ + "java", + "scala" + ], + "dependencies": [ + { + "uri": "file:///workspace/lib" + } + ], + "capabilities": { + "canCompile": true, + "canTest": false, + "canRun": true, + "canDebug": false + }, + "dataKind": "scala", + "data": { + "scalaOrganization": "org.scala-lang", + "scalaVersion": "", + "scalaBinaryVersion": "2.13", + "platform": 1, + "jars": [ + "file:///coursier-cache/https/repo1.maven.org/maven2/org/scala-lang/scala-compiler//scala-compiler-.jar", + "file:///coursier-cache/https/repo1.maven.org/maven2/org/scala-lang/scala-reflect//scala-reflect-.jar", + "file:///coursier-cache/https/repo1.maven.org/maven2/org/scala-lang/scala-library//scala-library-.jar", + "file:///coursier-cache/https/repo1.maven.org/maven2/io/github/java-diff-utils/java-diff-utils//java-diff-utils-.jar", + "file:///coursier-cache/https/repo1.maven.org/maven2/org/jline/jline//jline-.jar", + "file:///coursier-cache/https/repo1.maven.org/maven2/net/java/dev/jna/jna//jna-.jar" + ], + "jvmBuildTarget": { + "javaHome": "java-home", + "javaVersion": "" + } + } + }, + { + "id": { + "uri": "file:///workspace/lib" + }, + "displayName": "lib", + "baseDirectory": "file:///workspace/lib", + "tags": [ + "library", + "application" + ], + "languageIds": [ + "java", + "scala" + ], + "dependencies": [], + "capabilities": { + "canCompile": true, + "canTest": false, + "canRun": true, + "canDebug": false + }, + "dataKind": "scala", + "data": { + "scalaOrganization": "org.scala-lang", + "scalaVersion": "", + "scalaBinaryVersion": "2.13", + "platform": 1, + "jars": [ + "file:///coursier-cache/https/repo1.maven.org/maven2/org/scala-lang/scala-compiler//scala-compiler-.jar", + "file:///coursier-cache/https/repo1.maven.org/maven2/org/scala-lang/scala-reflect//scala-reflect-.jar", + "file:///coursier-cache/https/repo1.maven.org/maven2/org/scala-lang/scala-library//scala-library-.jar", + "file:///coursier-cache/https/repo1.maven.org/maven2/io/github/java-diff-utils/java-diff-utils//java-diff-utils-.jar", + "file:///coursier-cache/https/repo1.maven.org/maven2/org/jline/jline//jline-.jar", + "file:///coursier-cache/https/repo1.maven.org/maven2/net/java/dev/jna/jna//jna-.jar" + ], + "jvmBuildTarget": { + "javaHome": "java-home", + "javaVersion": "" + } + } + }, + { + "id": { + "uri": "file:///workspace/mill-build" + }, + "displayName": "mill-build/", + "baseDirectory": "file:///workspace/mill-build", + "tags": [ + "library", + "application" + ], + "languageIds": [ + "java", + "scala" + ], + "dependencies": [], + "capabilities": { + "canCompile": true, + "canTest": false, + "canRun": true, + "canDebug": false + }, + "dataKind": "scala", + "data": { + "scalaOrganization": "org.scala-lang", + "scalaVersion": "", + "scalaBinaryVersion": "3", + "platform": 1, + "jars": [ + "file:///coursier-cache/https/repo1.maven.org/maven2/org/scala-lang/scala3-compiler_3//scala3-compiler_3-.jar", + "file:///coursier-cache/https/repo1.maven.org/maven2/org/scala-lang/scala3-library_3//scala3-library_3-.jar", + "file:///coursier-cache/https/repo1.maven.org/maven2/org/scala-lang/scala3-interfaces//scala3-interfaces-.jar", + "file:///coursier-cache/https/repo1.maven.org/maven2/org/scala-lang/tasty-core_3//tasty-core_3-.jar", + "file:///coursier-cache/https/repo1.maven.org/maven2/org/scala-lang/modules/scala-asm//scala-asm-.jar", + "file:///coursier-cache/https/repo1.maven.org/maven2/org/scala-sbt/compiler-interface//compiler-interface-.jar", + "file:///coursier-cache/https/repo1.maven.org/maven2/org/jline/jline-reader//jline-reader-.jar", + "file:///coursier-cache/https/repo1.maven.org/maven2/org/jline/jline-terminal//jline-terminal-.jar", + "file:///coursier-cache/https/repo1.maven.org/maven2/org/jline/jline-terminal-jna//jline-terminal-jna-.jar", + "file:///coursier-cache/https/repo1.maven.org/maven2/org/scala-lang/scala-library//scala-library-.jar", + "file:///coursier-cache/https/repo1.maven.org/maven2/org/scala-sbt/util-interface//util-interface-.jar", + "file:///coursier-cache/https/repo1.maven.org/maven2/org/jline/jline-native//jline-native-.jar", + "file:///coursier-cache/https/repo1.maven.org/maven2/net/java/dev/jna/jna//jna-.jar" + ], + "jvmBuildTarget": { + "javaHome": "java-home", + "javaVersion": "" + } + } + }, + { + "id": { + "uri": "file:///workspace/mill-synthetic-root-target" + }, + "displayName": "mill-synthetic-root", + "baseDirectory": "file:///workspace", + "tags": [ + "manual" + ], + "languageIds": [], + "dependencies": [], + "capabilities": { + "canCompile": false, + "canTest": false, + "canRun": false, + "canDebug": false + } + }, + { + "id": { + "uri": "file:///workspace/thing" + }, + "displayName": "thing", + "baseDirectory": "file:///workspace/thing", + "tags": [ + "library", + "application" + ], + "languageIds": [ + "java", + "scala" + ], + "dependencies": [], + "capabilities": { + "canCompile": true, + "canTest": false, + "canRun": true, + "canDebug": false + }, + "dataKind": "scala", + "data": { + "scalaOrganization": "org.scala-lang", + "scalaVersion": "", + "scalaBinaryVersion": "2.13", + "platform": 1, + "jars": [ + "file:///coursier-cache/https/repo1.maven.org/maven2/org/scala-lang/scala-compiler//scala-compiler-.jar", + "file:///coursier-cache/https/repo1.maven.org/maven2/org/scala-lang/scala-reflect//scala-reflect-.jar", + "file:///coursier-cache/https/repo1.maven.org/maven2/org/scala-lang/scala-library//scala-library-.jar", + "file:///coursier-cache/https/repo1.maven.org/maven2/io/github/java-diff-utils/java-diff-utils//java-diff-utils-.jar", + "file:///coursier-cache/https/repo1.maven.org/maven2/org/jline/jline//jline-.jar", + "file:///coursier-cache/https/repo1.maven.org/maven2/net/java/dev/jna/jna//jna-.jar" + ], + "jvmBuildTarget": { + "javaHome": "java-home", + "javaVersion": "" + } + } + } + ] +} \ No newline at end of file diff --git a/integration/ide/bsp-server-reload/src/BspServerReloadTests.scala b/integration/ide/bsp-server-reload/src/BspServerReloadTests.scala new file mode 100644 index 00000000000..29480685d08 --- /dev/null +++ b/integration/ide/bsp-server-reload/src/BspServerReloadTests.scala @@ -0,0 +1,203 @@ +package mill.integration + +import ch.epfl.scala.{bsp4j => b} +import mill.api.BuildInfo +import mill.bsp.Constants +import mill.integration.BspServerTestUtil._ +import mill.testkit.UtestIntegrationTestSuite +import utest._ + +import scala.concurrent.{Await, Promise} +import scala.concurrent.duration.DurationInt +import scala.jdk.CollectionConverters._ +import scala.util.Success + +object BspServerReloadTests extends UtestIntegrationTestSuite { + override protected def workspaceSourcePath: os.Path = + super.workspaceSourcePath / "project" + + def tests = Tests { + test("reload") - integrationTest { tester => + import tester._ + + val startSnapshotsPath = super.workspaceSourcePath / "snapshots" / "reload" / "start" + val afterChangesSnapshotsPath = super.workspaceSourcePath / "snapshots" / "reload" / "changed" + + os.copy.over(workspacePath / "build.mill.base", workspacePath / "build.mill") + + eval( + "mill.bsp.BSP/install", + stdout = os.Inherit, + stderr = os.Inherit, + check = true, + env = Map("MILL_MAIN_CLI" -> tester.millExecutable.toString) + ) + + var didChangePromise = Promise[b.DidChangeBuildTarget]() + + val client = new DummyBuildClient { + override def onBuildTargetDidChange(params: b.DidChangeBuildTarget): Unit = + didChangePromise.complete(Success(params)) + } + + withBspServer( + workspacePath, + millTestSuiteEnv, + client = client + ) { (buildServer, initRes) => + + compareWithGsonSnapshot( + initRes, + startSnapshotsPath / "initialize-build-result.json", + normalizedLocalValues = Seq( + BuildInfo.millVersion -> "", + Constants.bspProtocolVersion -> "" + ) + ) + + val normalizedLocalValues = + normalizeLocalValuesForTesting(workspacePath) ++ scalaVersionNormalizedValues() + + val buildTargets = buildServer.workspaceBuildTargets().get() + compareWithGsonSnapshot( + buildTargets, + startSnapshotsPath / "workspace-build-targets.json", + normalizedLocalValues = normalizedLocalValues + ) + + didChangePromise = Promise[b.DidChangeBuildTarget]() + os.copy.over( + workspacePath / "build.mill.deletion-and-renaming", + workspacePath / "build.mill" + ) + + val didChangeParams = Await.result(didChangePromise.future, 1.minute) + + def eventData(event: b.BuildTargetEvent): (String, b.BuildTargetEventKind) = + (event.getTarget.getUri.split("/").last, event.getKind) + + val expectedChanges = Set( + "thing" -> b.BuildTargetEventKind.DELETED, + "app" -> b.BuildTargetEventKind.DELETED, + "my-app" -> b.BuildTargetEventKind.CREATED, + "lib" -> b.BuildTargetEventKind.CHANGED, + "mill-build" -> b.BuildTargetEventKind.CHANGED + ) + val changes = didChangeParams.getChanges().asScala.map(eventData).toSet + assert(expectedChanges == changes) + + val afterChangesBuildTargets = buildServer.workspaceBuildTargets().get() + compareWithGsonSnapshot( + afterChangesBuildTargets, + afterChangesSnapshotsPath / "workspace-build-targets.json", + normalizedLocalValues = normalizedLocalValues + ) + } + } + + test("broken") - integrationTest { tester => + import tester._ + + val startSnapshotsPath = super.workspaceSourcePath / "snapshots" / "broken" / "start" + val afterChangesSnapshotsPath = super.workspaceSourcePath / "snapshots" / "broken" / "changed" + val afterChangesSnapshotsPath0 = super.workspaceSourcePath / "snapshots" / "broken" / "back" + + os.copy.over(workspacePath / "build.mill.broken", workspacePath / "build.mill") + + var didChangePromise = Promise[b.DidChangeBuildTarget]() + + val client = new DummyBuildClient { + override def onBuildTargetDidChange(params: b.DidChangeBuildTarget): Unit = + didChangePromise.complete(Success(params)) + } + + withBspServer( + workspacePath, + millTestSuiteEnv, + client = client, + millExecutableNoBspFile = Some(tester.millExecutable) + ) { (buildServer, initRes) => + + compareWithGsonSnapshot( + initRes, + startSnapshotsPath / "initialize-build-result.json", + normalizedLocalValues = Seq( + BuildInfo.millVersion -> "", + Constants.bspProtocolVersion -> "" + ) + ) + + val normalizedLocalValues = + normalizeLocalValuesForTesting(workspacePath) ++ scalaVersionNormalizedValues() + + val buildTargets = buildServer.workspaceBuildTargets().get() + compareWithGsonSnapshot( + buildTargets, + startSnapshotsPath / "workspace-build-targets.json", + normalizedLocalValues = normalizedLocalValues + ) + + def eventData(event: b.BuildTargetEvent): (String, b.BuildTargetEventKind) = + (event.getTarget.getUri.split("/").last, event.getKind) + + didChangePromise = Promise[b.DidChangeBuildTarget]() + os.copy.over(workspacePath / "build.mill.base", workspacePath / "build.mill") + + val didChangeParams = Await.result(didChangePromise.future, 1.minute) + + val expectedChanges = Set( + "thing" -> b.BuildTargetEventKind.CREATED, + "app" -> b.BuildTargetEventKind.CREATED, + "lib" -> b.BuildTargetEventKind.CREATED, + "mill-build" -> b.BuildTargetEventKind.CHANGED + ) + val changes = didChangeParams.getChanges().asScala.map(eventData).toSet + assert(expectedChanges == changes) + + val afterChangesBuildTargets = buildServer.workspaceBuildTargets().get() + compareWithGsonSnapshot( + afterChangesBuildTargets, + afterChangesSnapshotsPath / "workspace-build-targets.json", + normalizedLocalValues = normalizedLocalValues + ) + + didChangePromise = Promise[b.DidChangeBuildTarget]() + os.copy.over(workspacePath / "build.mill.broken", workspacePath / "build.mill") + + val didChangeParams0 = Await.result(didChangePromise.future, 1.minute) + + val expectedChanges0 = Set( + "mill-build" -> b.BuildTargetEventKind.CHANGED + ) + val changes0 = didChangeParams0.getChanges().asScala.map(eventData).toSet + assert(expectedChanges0 == changes0) + + val afterChangesBuildTargets0 = buildServer.workspaceBuildTargets().get() + compareWithGsonSnapshot( + afterChangesBuildTargets0, + afterChangesSnapshotsPath0 / "workspace-build-targets.json", + normalizedLocalValues = normalizedLocalValues + ) + + val targetsMap = afterChangesBuildTargets0.getTargets.asScala + .map(target => target.getId.getUri.split("/").last -> target.getId) + .toMap + + val buildCompileRes = buildServer + .buildTargetCompile(new b.CompileParams(List(targetsMap("mill-build")).asJava)) + .get() + val appCompileRes = buildServer + .buildTargetCompile(new b.CompileParams(List(targetsMap("app")).asJava)) + .get() + assert( + buildCompileRes.getStatusCode == b.StatusCode.ERROR, + appCompileRes.getStatusCode == b.StatusCode.OK + ) + } + } + + // TODO Watch meta-meta-build + // TODO Start on broken meta-meta-build + // TODO Do more than just calling workspaceBuildTargets on main build with broken meta-build + } +} diff --git a/integration/ide/bsp-server/resources/snapshots/build-targets-jvm-test-environments.json b/integration/ide/bsp-server/resources/snapshots/build-targets-jvm-test-environments.json index d40f31a8cfb..56c4842b490 100644 --- a/integration/ide/bsp-server/resources/snapshots/build-targets-jvm-test-environments.json +++ b/integration/ide/bsp-server/resources/snapshots/build-targets-jvm-test-environments.json @@ -21,7 +21,6 @@ "environmentVariables": {}, "mainClasses": [] }, - { "target": { "uri": "file:///workspace/hello-java" diff --git a/integration/ide/bsp-server/resources/snapshots/initialize-build-result.json b/integration/ide/bsp-server/resources/snapshots/initialize-build-result.json index 3e7a009526d..dd49a77e8a9 100644 --- a/integration/ide/bsp-server/resources/snapshots/initialize-build-result.json +++ b/integration/ide/bsp-server/resources/snapshots/initialize-build-result.json @@ -32,7 +32,7 @@ "dependencyModulesProvider": true, "resourcesProvider": true, "outputPathsProvider": true, - "buildTargetChangedProvider": false, + "buildTargetChangedProvider": true, "jvmRunEnvironmentProvider": true, "jvmTestEnvironmentProvider": true, "canReload": true, diff --git a/integration/ide/bsp-server/src/BspServerTests.scala b/integration/ide/bsp-server/src/BspServerTests.scala index d744002f4ed..1520fce13e4 100644 --- a/integration/ide/bsp-server/src/BspServerTests.scala +++ b/integration/ide/bsp-server/src/BspServerTests.scala @@ -15,26 +15,6 @@ object BspServerTests extends UtestIntegrationTestSuite { override protected def workspaceSourcePath: os.Path = super.workspaceSourcePath / "project" - def transitiveDependenciesSubstitutions( - dependency: coursierapi.Dependency, - filter: coursierapi.Dependency => Boolean - ): Seq[(String, String)] = { - val fetchRes = coursierapi.Fetch.create() - .addDependencies(dependency) - .fetchResult() - fetchRes.getDependencies.asScala - .filter(filter) - .map { dep => - val organization = dep.getModule.getOrganization - val name = dep.getModule.getName - val prefix = (organization.split('.') :+ name).mkString("/") - def basePath(version: String): String = - s"$prefix/$version/$name-$version" - basePath(dep.getVersion) -> basePath(s"<$name-version>") - } - .toSeq - } - def tests: Tests = Tests { test("requestSnapshots") - integrationTest { tester => import tester._ @@ -50,25 +30,6 @@ object BspServerTests extends UtestIntegrationTestSuite { workspacePath, millTestSuiteEnv ) { (buildServer, initRes) => - val scala2Version = sys.props.getOrElse("TEST_SCALA_2_13_VERSION", ???) - val scala3Version = sys.props.getOrElse("MILL_SCALA_3_NEXT_VERSION", ???) - val scala2TransitiveSubstitutions = transitiveDependenciesSubstitutions( - coursierapi.Dependency.of( - "org.scala-lang", - "scala-compiler", - scala2Version - ), - _.getModule.getOrganization != "org.scala-lang" - ) - val scala3TransitiveSubstitutions = transitiveDependenciesSubstitutions( - coursierapi.Dependency.of( - "org.scala-lang", - "scala3-compiler_3", - scala3Version - ), - _.getModule.getOrganization != "org.scala-lang" - ) - val kotlinVersion = sys.props.getOrElse("TEST_KOTLIN_VERSION", ???) val kotlinTransitiveSubstitutions = transitiveDependenciesSubstitutions( coursierapi.Dependency.of( @@ -80,14 +41,8 @@ object BspServerTests extends UtestIntegrationTestSuite { ) val normalizedLocalValues = normalizeLocalValuesForTesting(workspacePath) ++ - scala2TransitiveSubstitutions ++ - scala3TransitiveSubstitutions ++ - kotlinTransitiveSubstitutions ++ - Seq( - scala2Version -> "", - scala3Version -> "", - kotlinVersion -> "" - ) + scalaVersionNormalizedValues() ++ + kotlinVersionNormalizedValues() compareWithGsonSnapshot( initRes, diff --git a/integration/package.mill b/integration/package.mill index 6b76fedb694..283e0acd738 100644 --- a/integration/package.mill +++ b/integration/package.mill @@ -118,6 +118,7 @@ object `package` extends RootModule { ) } trait IdeIntegrationCrossModule extends IntegrationCrossModule { + def moduleDeps = super.moduleDeps ++ Seq(`bsp-util`) def mvnDeps = super.mvnDeps() ++ Agg( build.Deps.bsp4j ) @@ -130,4 +131,11 @@ object `package` extends RootModule { PathRef(build.dist.installLocalTask(binFile = Task.Anon((Task.dest / name).toString()))()) } } + + object `bsp-util` extends build.MillScalaModule { + def moduleDeps = Seq(build.main.test, build.runner) // , build.testkit) + def mvnDeps = super.mvnDeps() ++ Agg( + build.Deps.bsp4j + ) + } } diff --git a/runner/src/mill/runner/MillBuildBootstrap.scala b/runner/src/mill/runner/MillBuildBootstrap.scala index 3e8f4758e52..9596485f2bd 100644 --- a/runner/src/mill/runner/MillBuildBootstrap.scala +++ b/runner/src/mill/runner/MillBuildBootstrap.scala @@ -13,7 +13,9 @@ import mill.runner.worker.api.MillScalaParser import mill.runner.meta.{CliImports, FileImportGraph, MillBuildRootModule, ScalaCompilerWorker} import java.io.File import java.net.URLClassLoader +import java.util.UUID +import scala.annotation.tailrec import scala.jdk.CollectionConverters.ListHasAsScala import scala.util.Using @@ -75,14 +77,17 @@ class MillBuildBootstrap( } Watching.Result( - watched = runnerState.frames.flatMap(f => f.evalWatched ++ f.moduleWatched), + watched = runnerState.watched, error = runnerState.errorOpt, result = runnerState ) } - def evaluateRec(depth: Int): RunnerState = { - // println(s"+evaluateRec($depth) " + recRoot(projectRoot, depth)) + def evaluateRec( + depth: Int, + closeEvaluator: Boolean = true + ): RunnerState = { + // println(s"+evaluateRec($depth, $closeEvaluator) " + recRoot(projectRoot, depth)) val prevFrameOpt = prevRunnerState.frames.lift(depth) val prevOuterFrameOpt = prevRunnerState.frames.lift(depth - 1) @@ -93,7 +98,7 @@ class MillBuildBootstrap( // On this level we typically want to assume a Mill project, which means we want to require an existing `build.mill`. // Unfortunately, some targets also make sense without a `build.mill`, e.g. the `init` command. // Hence, we only report a missing `build.mill` as a problem if the command itself does not succeed. - lazy val state = evaluateRec(depth + 1) + lazy val state = evaluateRec(depth + 1, closeEvaluator = closeEvaluator) if ( rootBuildFileNames.asScala.exists(rootBuildFileName => os.exists(recRoot(projectRoot, depth) / rootBuildFileName) @@ -121,7 +126,7 @@ class MillBuildBootstrap( output ) - if (parsedScriptFiles.metaBuild) evaluateRec(depth + 1) + if (parsedScriptFiles.metaBuild) evaluateRec(depth + 1, closeEvaluator = closeEvaluator) else { val bootstrapModule = new MillBuildRootModule.BootstrapModule()( @@ -187,7 +192,7 @@ class MillBuildBootstrap( case Result.Failure(err) => nestedState.add(errorOpt = Some(err)) case Result.Success(rootModule) => - Using.resource(makeEvaluator( + def makeEvaluator0 = makeEvaluator( prevFrameOpt.map(_.workerCache).getOrElse(Map.empty), nestedState.frames.headOption.map(_.codeSignatures).getOrElse(Map.empty), rootModule, @@ -210,19 +215,26 @@ class MillBuildBootstrap( .getOrElse(0), depth, actualBuildFileName = nestedState.buildFile - )) { evaluator => - if (depth == requestedDepth) processFinalTargets(nestedState, rootModule, evaluator) + ) + + def proceed(evaluator: () => EvaluatorApi): RunnerState = + // FIXME If this throws after having called evaluator(), the evaluator might not be closed + if (depth == requestedDepth) processFinalTargets(nestedState, rootModule, evaluator()) else if (depth <= requestedDepth) nestedState else { processRunClasspath( nestedState, rootModule, - evaluator, + evaluator(), prevFrameOpt, prevOuterFrameOpt ) } - } + + if (closeEvaluator) + Using.resource(makeEvaluator0)(ev => proceed(() => ev)) + else + proceed(() => makeEvaluator0) } } @@ -294,8 +306,30 @@ class MillBuildBootstrap( // Make sure we close the old classloader every time we create a new // one, to avoid memory leaks prevFrameOpt.foreach(_.classLoaderOpt.foreach(_.close())) + + // Copy the compilation output to a dedicated directory, so that it's not + // deleted or overwritten by other Mill runners while we use it + val (tmpDir0, runClasspath0) = { + val tmpDir = output / "mill-bootstrap" / UUID.randomUUID().toString + val cp = runClasspath.map(os.Path(_)) + val updatedCp = cp.map { elem => + if (elem.startsWith(output)) { + @tailrec def dest(count: Int = 0): os.Path = { + val suffix = if (count == 0) "" else s"-$count" + val candidate = tmpDir / (elem.last + suffix) + if (os.exists(candidate)) dest(count + 1) + else candidate + } + val dest0 = dest() + os.copy(elem, dest0, createFolders = true) + dest0 + } else + elem + } + (tmpDir, updatedCp) + } val cl = new RunnerState.URLClassLoader( - runClasspath.map(os.Path(_).toNIO.toUri.toURL).toArray, + runClasspath0.map(_.toNIO.toUri.toURL).toArray, null ) { val sharedCl = classOf[MillBuildBootstrap].getClassLoader @@ -303,6 +337,11 @@ class MillBuildBootstrap( override def findClass(name: String): Class[?] = if (sharedPrefixes.exists(name.startsWith)) sharedCl.loadClass(name) else super.findClass(name) + override def close(): Unit = { + super.close() + // might fail on Windows sometimes… + os.remove.all(tmpDir0) + } } cl } else { diff --git a/runner/src/mill/runner/MillMain.scala b/runner/src/mill/runner/MillMain.scala index f63122fe48d..7bbd600a0c1 100644 --- a/runner/src/mill/runner/MillMain.scala +++ b/runner/src/mill/runner/MillMain.scala @@ -2,14 +2,15 @@ package mill.runner import mill.{api, define} -import java.io.{PipedInputStream, PrintStream} +import java.io.{InputStream, PipedInputStream, PrintStream} import java.nio.file.Files import java.nio.file.StandardOpenOption import java.util.Locale import scala.jdk.CollectionConverters.* import scala.util.Properties -import mill.api.internal.{BspServerResult, internal} +import mill.api.internal.{BspServerHandle, BspServerResult, EvaluatorApi, internal} import mill.api.{DummyInputStream, Logger, MillException, Result, SystemStreams} +import mill.bsp.worker.BspWorkerImpl import mill.constants.{OutFiles, ServerFiles, Util} import mill.client.lock.Lock import mill.define.WorkspaceRoot @@ -17,8 +18,9 @@ import mill.util.BuildInfo import mill.runner.meta.ScalaCompilerWorker import mill.internal.{Colors, PromptLogger} import java.lang.reflect.InvocationTargetException +import scala.concurrent.Future import scala.util.control.NonFatal -import scala.util.Using +import scala.util.{Failure, Success, Using} import mill.server.Server @internal @@ -91,7 +93,17 @@ object MillMain { systemExit: Int => Nothing, serverDir: os.Path ): (Boolean, RunnerState) = { - val streams = streams0 + val bspMode = args.headOption.contains("--bsp") + val streams = + if (bspMode) + // In BSP mode, don't let anything other than the BSP server write to stdout and read from stdin + new SystemStreams( + out = streams0.err, + err = streams0.err, + in = InputStream.nullInputStream() + ) + else + streams0 mill.define.SystemStreams.withStreams(streams) { os.SubProcess.env.withValue(env) { MillCliConfigParser.parse(args) match { @@ -132,6 +144,10 @@ object MillMain { ) (false, RunnerState.empty) + case Result.Success(config) if config.bsp.value != bspMode => + streams.err.println("--bsp must be passed in as the first argument") + (false, RunnerState.empty) + case Result.Success(config) if Seq( config.interactive.value, @@ -235,7 +251,7 @@ object MillMain { requestedMetaLevel = config.metaLevel, config.allowPositional.value, systemExit = systemExit, - streams0 = streams0, + streams0 = streams, selectiveExecution = config.watch.value, scalaCompilerWorker = scalaCompilerWorker, offline = config.offline.value @@ -245,19 +261,65 @@ object MillMain { } } + // special BSP mode, in which we spawn a server and register the current evaluator when-ever we start to eval a dedicated command if (bspMode) { - - runBspSession( - streams, - (prevRunnerState, splitStreams) => - runMillBootstrap( - false, - prevRunnerState, - Seq("version"), - splitStreams - ).result + val splitOut = new mill.internal.MultiStream( + streams.err, + os.write.outputStream( + WorkspaceRoot.workspaceRoot / OutFiles.out / "mill-bsp.out.log" + ) + ) + val splitErr = new mill.internal.MultiStream( + streams.err, + os.write.outputStream( + WorkspaceRoot.workspaceRoot / OutFiles.out / "mill-bsp.err.log" + ) ) + var prevRunnerStateOpt = Option.empty[RunnerState] + val bspServerHandle = startBspServer(streams0) + var keepGoing = true + while (keepGoing) { + val watchRes = runMillBootstrap( + false, + prevRunnerStateOpt, + Seq("version"), + new SystemStreams(splitOut, splitErr, InputStream.nullInputStream()) + ) + + for (err <- watchRes.error) + streams.err.println(err) + + prevRunnerStateOpt = Some(watchRes.result) + + val sessionResultFuture = bspServerHandle.startSession( + watchRes.result.frames.flatMap(_.evaluator), + errored = watchRes.error.nonEmpty + ) + + val res = Watching.futureWatchWait(watchRes.watched, sessionResultFuture) + + // Suspend any BSP request until the next call to startSession + // (that is, until we've attempted to re-compile the build) + bspServerHandle.resetSession() + + res match { + case None => + // Some watched meta-build files changed + case Some(Failure(ex)) => + // What does this correspond to? + case Some(Success(BspServerResult.ReloadWorkspace)) => + // reload asked by client + case Some(Success(BspServerResult.Shutdown)) => + // shutdown asked by client + keepGoing = false + // should make the lsp4j-managed BSP server exit + streams0.in.close() + } + } + + splitErr.println("Exiting BSP runner loop") + (true, RunnerState(None, Nil, None)) } else { // When starting a --watch, clear the `mill-selective-execution.json` @@ -300,77 +362,25 @@ object MillMain { } } - def runBspSession( - streams0: SystemStreams, - runMillBootstrap: (Option[RunnerState], SystemStreams) => RunnerState - ): Result[BspServerResult] = { - val splitOut = new mill.internal.MultiStream( - streams0.out, - os.write.outputStream( - WorkspaceRoot.workspaceRoot / OutFiles.out / "mill-bsp/out.log", - createFolders = true - ) - ) - val splitErr = new mill.internal.MultiStream( - streams0.out, - os.write.outputStream( - WorkspaceRoot.workspaceRoot / OutFiles.out / "mill-bsp/err.log", - createFolders = true - ) - ) - val splitStreams = new SystemStreams(splitOut, splitErr, streams0.in) - - mill.define.SystemStreams.withStreams(splitStreams) { - try { - splitStreams.err.println("Trying to load BSP server...") - - val wsRoot = WorkspaceRoot.workspaceRoot - val logDir = wsRoot / OutFiles.out / "mill-bsp" - val bspServerHandleRes = { - os.makeDir.all(logDir) - mill.bsp.worker.BspWorkerImpl.startBspServer( - define.WorkspaceRoot.workspaceRoot, - splitStreams, - logDir, - true - ) - } + def startBspServer(streams0: SystemStreams): BspServerHandle = { - streams0.err.println("BSP server started") - - val runSessionRes = bspServerHandleRes.flatMap { bspServerHandle => - try { - var repeatForBsp = true - var bspRes: Option[Result[BspServerResult]] = None - var prevRunnerState: Option[RunnerState] = None - while (repeatForBsp) { - repeatForBsp = false - val runnerState = runMillBootstrap(prevRunnerState, splitStreams) - val runSessionRes = - bspServerHandle.runSession(runnerState.frames.flatMap(_.evaluator)) - prevRunnerState = Some(runnerState) - repeatForBsp = runSessionRes == BspServerResult.ReloadWorkspace - bspRes = Some(runSessionRes) - streams0.err.println(s"BSP session returned with $runSessionRes") - } + streams0.err.println("Trying to load BSP server...") - // should make the lsp4j-managed BSP server exit - streams0.in.close() + val wsRoot = WorkspaceRoot.workspaceRoot + val logDir = wsRoot / OutFiles.out / "mill-bsp" + os.makeDir.all(logDir) - bspRes.get - } finally bspServerHandle.close() - } + val bspServerHandleRes = + BspWorkerImpl.startBspServer( + define.WorkspaceRoot.workspaceRoot, + streams0, + logDir, + true + ).get - splitErr.println( - s"Exiting BSP runner loop. Stopping BSP server. Last result: $runSessionRes" - ) - runSessionRes - } finally { + streams0.err.println("BSP server started") - splitErr.close() - splitOut.close() - } - } + bspServerHandleRes } private[runner] def parseThreadCount( diff --git a/runner/src/mill/runner/RunnerState.scala b/runner/src/mill/runner/RunnerState.scala index 958963b5ce5..2cdfe97171b 100644 --- a/runner/src/mill/runner/RunnerState.scala +++ b/runner/src/mill/runner/RunnerState.scala @@ -39,6 +39,9 @@ case class RunnerState( ): RunnerState = { this.copy(frames = Seq(frame) ++ frames, errorOpt = errorOpt) } + + def watched: Seq[Watchable] = + frames.flatMap(f => f.evalWatched ++ f.moduleWatched) } object RunnerState { @@ -104,7 +107,7 @@ object RunnerState { ) implicit val loggedRw: ReadWriter[Logged] = macroRW - def empty: Frame = Frame(Map.empty, Nil, Nil, Map.empty, None, Nil, None, null) + def empty: Frame = Frame(Map.empty, Nil, Nil, Map.empty, None, Nil, None, None) } } diff --git a/runner/src/mill/runner/Watching.scala b/runner/src/mill/runner/Watching.scala index 48fdba512be..8413182ac03 100644 --- a/runner/src/mill/runner/Watching.scala +++ b/runner/src/mill/runner/Watching.scala @@ -8,6 +8,8 @@ import mill.api.internal.internal import java.io.InputStream import scala.annotation.tailrec +import scala.concurrent.Future +import scala.util.Try /** * Logic around the "watch and wait" functionality in Mill: re-run on change, @@ -120,4 +122,32 @@ object Watching { new PathRef(os.Path(p), quick, sig, PathRef.Revalidate.Once).sig case Watchable.Value(f, sig, pretty) => sig } + + /** + * Watches files and a future + * + * Returns only when either: + * * a watchable changed + * * the future is completed + * + * @param watched [[Watchable]]s to watch + * @param future [[Future]] instance to look for completion + * @return if the future completed, the future result wrapped in `Some`; else `None` + */ + def futureWatchWait[T](watched: Seq[Watchable], future: Future[T]): Option[Try[T]] = { + @tailrec def statWatchWait0(): Option[Try[T]] = + if (watched.forall(Watching.validate(_))) + // don't use getOrElse for @tailrec to work + future.value match { + case Some(res) => Some(res) + case None => + Thread.sleep(100) + statWatchWait0() + } + else + future.value + + statWatchWait0() + } + } diff --git a/scalalib/test/src/mill/scalalib/api/ZincWorkerUtilTests.scala b/scalalib/test/src/mill/scalalib/api/JvmWorkerUtilTests.scala similarity index 100% rename from scalalib/test/src/mill/scalalib/api/ZincWorkerUtilTests.scala rename to scalalib/test/src/mill/scalalib/api/JvmWorkerUtilTests.scala diff --git a/testkit/src/mill/testkit/IntegrationTesterBase.scala b/testkit/src/mill/testkit/IntegrationTesterBase.scala index 81c985841eb..afe828bf58f 100644 --- a/testkit/src/mill/testkit/IntegrationTesterBase.scala +++ b/testkit/src/mill/testkit/IntegrationTesterBase.scala @@ -72,7 +72,9 @@ trait IntegrationTesterBase { if (os.exists(outDir)) { val serverPath0 = outDir / (if (clientServerMode) millServer else millNoServer) - for (serverPath <- os.list.stream(serverPath0)) os.remove(serverPath / processId) + if (os.exists(serverPath0)) + for (serverPath <- os.list.stream(serverPath0)) + os.remove(serverPath / processId) Thread.sleep(500) // give a moment for the server to notice the file is gone and exit }