diff --git a/bsp/src/mill/bsp/BSP.scala b/bsp/src/mill/bsp/BSP.scala index 79392b9903d..8d6f87dcf26 100644 --- a/bsp/src/mill/bsp/BSP.scala +++ b/bsp/src/mill/bsp/BSP.scala @@ -1,24 +1,16 @@ package mill.bsp -import java.io.{InputStream, PrintStream} -import scala.concurrent.{Await, Promise} -import scala.concurrent.duration.Duration -import mill.api.{Ctx, DummyInputStream, Logger, PathRef, Result, SystemStreams} +import mill.api.{Ctx, PathRef} import mill.{Agg, T, BuildInfo => MillBuildInfo} -import mill.define.{Command, Discover, ExternalModule, Task} +import mill.define.{Command, Discover, ExternalModule} import mill.eval.Evaluator -import mill.main.{BspServerHandle, BspServerResult, BspServerStarter} import mill.util.Util.millProjectModule -import mill.scalalib.{CoursierModule, Dep} -import mill.util.PrintLogger -import os.Path +import mill.scalalib.CoursierModule -object BSP extends ExternalModule with CoursierModule with BspServerStarter { +object BSP extends ExternalModule with CoursierModule { lazy val millDiscover: Discover[this.type] = Discover[this.type] - private[this] val millServerHandle = Promise[BspServerHandle]() - private def bspWorkerLibs: T[Agg[PathRef]] = T { millProjectModule("mill-bsp-worker", repositoriesTask()) } @@ -47,7 +39,7 @@ object BSP extends ExternalModule with CoursierModule with BspServerStarter { libUrls.mkString("\n"), createFolders = true ) - BspWorker(T.ctx(), Some(libUrls)).map(_.createBspConnection(jobs, Constants.serverName)) + createBspConnection(jobs, Constants.serverName) } /** @@ -58,62 +50,53 @@ object BSP extends ExternalModule with CoursierModule with BspServerStarter { */ def startSession(ev: Evaluator): Command[BspServerResult] = T.command { T.log.errorStream.println("BSP/startSession: Starting BSP session") - val serverHandle: BspServerHandle = Await.result(millServerHandle.future, Duration.Inf) - val res = serverHandle.runSession(ev) + val res = BspContext.bspServerHandle.runSession(ev) T.log.errorStream.println(s"BSP/startSession: Finished BSP session, result: ${res}") res } - override def startBspServer( - initialEvaluator: Option[Evaluator], - streams: SystemStreams, - logStream: Option[PrintStream], - workspaceDir: os.Path, - ammoniteHomeDir: os.Path, - canReload: Boolean, - serverHandle: Option[Promise[BspServerHandle]] = None - ): BspServerResult = { - val ctx = new Ctx.Workspace with Ctx.Home with Ctx.Log { - override def workspace: Path = workspaceDir - override def home: Path = ammoniteHomeDir - // This all goes to the BSP log file mill-bsp.stderr - override def log: Logger = new Logger { - override def colored: Boolean = false - override def systemStreams: SystemStreams = new SystemStreams( - out = streams.out, - err = streams.err, - in = DummyInputStream - ) - override def info(s: String): Unit = streams.err.println(s) - override def error(s: String): Unit = streams.err.println(s) - override def ticker(s: String): Unit = streams.err.println(s) - override def debug(s: String): Unit = streams.err.println(s) - override def debugEnabled: Boolean = true - } - } + private def createBspConnection( + jobs: Int, + serverName: String + )(implicit ctx: Ctx): (PathRef, ujson.Value) = { + // we create a json connection file + val bspFile = ctx.workspace / Constants.bspDir / s"${serverName}.json" + if (os.exists(bspFile)) ctx.log.info(s"Overwriting BSP connection file: ${bspFile}") + else ctx.log.info(s"Creating BSP connection file: ${bspFile}") + val withDebug = ctx.log.debugEnabled + if (withDebug) ctx.log.debug( + "Enabled debug logging for the BSP server. If you want to disable it, you need to re-run this install command without the --debug option." + ) + val connectionContent = bspConnectionJson(jobs, withDebug) + os.write.over(bspFile, connectionContent, createFolders = true) + (PathRef(bspFile), upickle.default.read[ujson.Value](connectionContent)) + } - val worker = BspWorker(ctx) + private def bspConnectionJson(jobs: Int, debug: Boolean): String = { + val props = sys.props + val millPath = props + .get("mill.main.cli") + // we assume, the classpath is an executable jar here + .orElse(props.get("java.class.path")) + .getOrElse(throw new IllegalStateException("System property 'java.class.path' not set")) - worker match { - case Result.Success(worker) => - worker.startBspServer( - initialEvaluator, - streams, - logStream.getOrElse(streams.err), - workspaceDir / Constants.bspDir, - canReload, - Seq(millServerHandle) ++ serverHandle.toSeq - ) - case f: Result.Failure[_] => - streams.err.println("Failed to start the BSP worker. " + f.msg) - BspServerResult.Failure - case f: Result.Exception => - streams.err.println("Failed to start the BSP worker. " + f.throwable) - BspServerResult.Failure - case f => - streams.err.println("Failed to start the BSP worker. " + f) - BspServerResult.Failure - } + upickle.default.write( + BspConfigJson( + name = "mill-bsp", + argv = Seq( + millPath, + "--bsp", + "--disable-ticker", + "--color", + "false", + "--jobs", + s"${jobs}" + ) ++ (if (debug) Seq("--debug") else Seq()), + millVersion = MillBuildInfo.millVersion, + bspVersion = Constants.bspProtocolVersion, + languages = Constants.languages + ) + ) } } diff --git a/bsp/src/mill/bsp/BspConfigJson.scala b/bsp/src/mill/bsp/BspConfigJson.scala new file mode 100644 index 00000000000..c18918f2baf --- /dev/null +++ b/bsp/src/mill/bsp/BspConfigJson.scala @@ -0,0 +1,15 @@ +package mill.bsp + +import upickle.default._ + +private case class BspConfigJson( + name: String, + argv: Seq[String], + millVersion: String, + bspVersion: String, + languages: Seq[String] +) + +private object BspConfigJson { + implicit val rw: ReadWriter[BspConfigJson] = macroRW +} diff --git a/bsp/src/mill/bsp/BspContext.scala b/bsp/src/mill/bsp/BspContext.scala new file mode 100644 index 00000000000..0baed2b20f3 --- /dev/null +++ b/bsp/src/mill/bsp/BspContext.scala @@ -0,0 +1,73 @@ +package mill.bsp + +import mill.api.{DummyInputStream, Logger, SystemStreams, internal} +import mill.eval.Evaluator + +import java.io.PrintStream +import scala.util.control.NonFatal + +object BspContext { + @volatile var bspServerHandle: BspServerHandle = null +} + +@internal +class BspContext(streams: SystemStreams, bspLogStream: Option[PrintStream], home: os.Path) { + // BSP mode, run with a simple evaluator command to inject the evaluator + // The command returns when the server exists or the workspace should be reloaded + // if the `lastResult` is `ReloadWorkspace` we re-run the script in a loop + + streams.err.println("Running in BSP mode with hardcoded startSession command") + + streams.err.println("Trying to load BSP server...") + BspContext.bspServerHandle = + try { + startBspServer( + initialEvaluator = None, + streams = streams, + logStream = bspLogStream, + canReload = true + ) match { + case Left(err) => sys.error(err) + case Right(res) => res + } + } catch { + case NonFatal(e) => + streams.err.println(s"Could not start BSP server. ${e.getMessage}") + throw e + } + + streams.err.println("BSP server started") + + def startBspServer( + initialEvaluator: Option[Evaluator], + streams: SystemStreams, + logStream: Option[PrintStream], + canReload: Boolean + ): Either[String, BspServerHandle] = { + val log: Logger = new Logger { + override def colored: Boolean = false + override def systemStreams: SystemStreams = new SystemStreams( + out = streams.out, + err = streams.err, + in = DummyInputStream + ) + + override def info(s: String): Unit = streams.err.println(s) + override def error(s: String): Unit = streams.err.println(s) + override def ticker(s: String): Unit = streams.err.println(s) + override def debug(s: String): Unit = streams.err.println(s) + override def debugEnabled: Boolean = true + } + + BspWorker(os.pwd, home, log).flatMap { worker => + os.makeDir.all(home / Constants.bspDir) + worker.startBspServer( + initialEvaluator, + streams, + logStream.getOrElse(streams.err), + home / Constants.bspDir, + canReload + ) + } + } +} diff --git a/main/src/mill/main/BspServerHandle.scala b/bsp/src/mill/bsp/BspServerHandle.scala similarity index 97% rename from main/src/mill/main/BspServerHandle.scala rename to bsp/src/mill/bsp/BspServerHandle.scala index 48ef795a9ea..a72ab4fe4fa 100644 --- a/main/src/mill/main/BspServerHandle.scala +++ b/bsp/src/mill/bsp/BspServerHandle.scala @@ -1,4 +1,4 @@ -package mill.main +package mill.bsp import mill.eval.Evaluator diff --git a/main/src/mill/main/BspServerResult.scala b/bsp/src/mill/bsp/BspServerResult.scala similarity index 98% rename from main/src/mill/main/BspServerResult.scala rename to bsp/src/mill/bsp/BspServerResult.scala index 139ff50becd..5a06bec185c 100644 --- a/main/src/mill/main/BspServerResult.scala +++ b/bsp/src/mill/bsp/BspServerResult.scala @@ -1,4 +1,4 @@ -package mill.main +package mill.bsp import mill.api.internal diff --git a/bsp/src/mill/bsp/BspServerStarterImpl.scala b/bsp/src/mill/bsp/BspServerStarterImpl.scala deleted file mode 100644 index 571372e9e71..00000000000 --- a/bsp/src/mill/bsp/BspServerStarterImpl.scala +++ /dev/null @@ -1,7 +0,0 @@ -package mill.bsp - -import mill.main.BspServerStarter - -object BspServerStarterImpl { - def get: BspServerStarter = BSP -} diff --git a/bsp/src/mill/bsp/BspWorker.scala b/bsp/src/mill/bsp/BspWorker.scala index 181eb1967e9..39093618d61 100644 --- a/bsp/src/mill/bsp/BspWorker.scala +++ b/bsp/src/mill/bsp/BspWorker.scala @@ -1,34 +1,21 @@ package mill.bsp -import mill.Agg -import mill.api.{Ctx, PathRef, Result, internal} -import mill.define.Task +import mill.api.{Ctx, Logger, SystemStreams, internal} import mill.eval.Evaluator -import mill.main.{BspServerHandle, BspServerResult} -import mill.api.SystemStreams +import os.Path import java.io.PrintStream import java.net.URL -import scala.concurrent.Promise -import scala.util.{Failure, Success, Try} @internal trait BspWorker { - - def createBspConnection( - jobs: Int, - serverName: String - )(implicit ctx: Ctx): (PathRef, ujson.Value) - def startBspServer( initialEvaluator: Option[Evaluator], streams: SystemStreams, logStream: PrintStream, logDir: os.Path, - canReload: Boolean, - serverHandles: Seq[Promise[BspServerHandle]] - ): BspServerResult - + canReload: Boolean + ): Either[String, BspServerHandle] } @internal @@ -37,57 +24,42 @@ object BspWorker { private[this] var worker: Option[BspWorker] = None def apply( - millCtx: Ctx.Workspace with Ctx.Home with Ctx.Log, + workspace: os.Path, + home0: os.Path, + log: Logger, workerLibs: Option[Seq[URL]] = None - ): Result[BspWorker] = { + ): Either[String, BspWorker] = { worker match { - case Some(x) => Result.Success(x) + case Some(x) => Right(x) case None => val urls = workerLibs.map { urls => - millCtx.log.debug("Using direct submitted worker libs") + log.debug("Using direct submitted worker libs") urls }.getOrElse { // load extra classpath entries from file val cpFile = - millCtx.workspace / Constants.bspDir / s"${Constants.serverName}-${mill.BuildInfo.millVersion}.resources" - if (!os.exists(cpFile)) return Result.Failure( + workspace / Constants.bspDir / s"${Constants.serverName}-${mill.BuildInfo.millVersion}.resources" + if (!os.exists(cpFile)) return Left( "You need to run `mill mill.bsp.BSP/install` before you can use the BSP server" ) // TODO: if outdated, we could regenerate the resource file and re-load the worker // read the classpath from resource file - millCtx.log.debug(s"Reading worker classpath from file: ${cpFile}") + log.debug(s"Reading worker classpath from file: ${cpFile}") os.read(cpFile).linesIterator.map(u => new URL(u)).toSeq } // create classloader with bsp.worker and deps - val cl = mill.api.ClassLoader.create(urls, getClass().getClassLoader())(millCtx) - - // check the worker version - Try { - val workerBuildInfo = cl.loadClass(Constants.bspWorkerBuildInfoClass) - workerBuildInfo.getMethod("millBspWorkerVersion").invoke(null) - } match { - case Success(mill.BuildInfo.millVersion) => // same as Mill, everything is good - case Success(workerVersion) => - millCtx.log.error( - s"""BSP worker version ($workerVersion) does not match Mill version (${mill.BuildInfo.millVersion}). - |You need to run `mill mill.bsp.BSP/install` again.""".stripMargin - ) - case Failure(e) => - millCtx.log.error( - s"""Could not validate worker version number. - |Error message: ${e.getMessage} - |""".stripMargin - ) - } + val cl = mill.api.ClassLoader.create(urls, getClass().getClassLoader())( + new Ctx.Home { override def home: Path = home0 } + ) val workerCls = cl.loadClass(Constants.bspWorkerImplClass) val ctr = workerCls.getConstructor() val workerImpl = ctr.newInstance().asInstanceOf[BspWorker] worker = Some(workerImpl) - Result.Success(workerImpl) + Right(workerImpl) } } diff --git a/bsp/worker/src/mill/bsp/worker/BspCompileProblemReporter.scala b/bsp/worker/src/mill/bsp/worker/BspCompileProblemReporter.scala index 736c4f326ee..b00b97f6b96 100644 --- a/bsp/worker/src/mill/bsp/worker/BspCompileProblemReporter.scala +++ b/bsp/worker/src/mill/bsp/worker/BspCompileProblemReporter.scala @@ -27,7 +27,7 @@ import scala.language.implicitConversions * back as part of the published diagnostics * as well as compile report */ -class BspCompileProblemReporter( +private class BspCompileProblemReporter( client: bsp.BuildClient, targetId: BuildTargetIdentifier, targetDisplayName: String, diff --git a/bsp/worker/src/mill/bsp/worker/BspConfigJson.scala b/bsp/worker/src/mill/bsp/worker/BspConfigJson.scala deleted file mode 100644 index 2c541a92629..00000000000 --- a/bsp/worker/src/mill/bsp/worker/BspConfigJson.scala +++ /dev/null @@ -1,18 +0,0 @@ -package mill.bsp.worker - -import ch.epfl.scala.bsp4j.BspConnectionDetails -import upickle.default._ - -import scala.jdk.CollectionConverters._ - -case class BspConfigJson( - name: String, - argv: Seq[String], - millVersion: String, - bspVersion: String, - languages: Seq[String] -) extends BspConnectionDetails(name, argv.asJava, millVersion, bspVersion, languages.asJava) - -object BspConfigJson { - implicit val rw: ReadWriter[BspConfigJson] = macroRW -} diff --git a/bsp/worker/src/mill/bsp/worker/BspTestReporter.scala b/bsp/worker/src/mill/bsp/worker/BspTestReporter.scala index 336d7ccef22..b357c75eeef 100644 --- a/bsp/worker/src/mill/bsp/worker/BspTestReporter.scala +++ b/bsp/worker/src/mill/bsp/worker/BspTestReporter.scala @@ -39,7 +39,7 @@ import java.io.{PrintWriter, StringWriter} * in case special arguments need to be passed to * the compiler before running the test task. */ -class BspTestReporter( +private class BspTestReporter( client: BuildClient, targetId: BuildTargetIdentifier, taskId: TaskId, diff --git a/bsp/worker/src/mill/bsp/worker/BspWorkerImpl.scala b/bsp/worker/src/mill/bsp/worker/BspWorkerImpl.scala index 42c73cef304..17ce6d9932d 100644 --- a/bsp/worker/src/mill/bsp/worker/BspWorkerImpl.scala +++ b/bsp/worker/src/mill/bsp/worker/BspWorkerImpl.scala @@ -1,76 +1,26 @@ package mill.bsp.worker import ch.epfl.scala.bsp4j.BuildClient -import mill.api.{Ctx, PathRef, internal} -import mill.{Agg, T, BuildInfo => MillBuildInfo} -import mill.bsp.{BSP, BspWorker, Constants} -import mill.define.Task +import mill.{BuildInfo => MillBuildInfo} +import mill.bsp.{BspServerHandle, BspServerResult, BspWorker, Constants} import mill.eval.Evaluator -import mill.main.{BspServerHandle, BspServerResult} import mill.api.SystemStreams import org.eclipse.lsp4j.jsonrpc.Launcher -import java.io.{InputStream, PrintStream, PrintWriter} +import java.io.{PrintStream, PrintWriter} import java.util.concurrent.Executors import scala.concurrent.duration.Duration import scala.concurrent.{Await, CancellationException, Promise} -import scala.util.chaining.scalaUtilChainingOps -@internal -class BspWorkerImpl() extends BspWorker { - - def bspConnectionJson(jobs: Int, debug: Boolean): String = { - val props = sys.props - val millPath = props - .get("mill.main.cli") - // we assume, the classpath is an executable jar here - .orElse(props.get("java.class.path")) - .getOrElse(throw new IllegalStateException("System property 'java.class.path' not set")) - - upickle.default.write( - BspConfigJson( - name = "mill-bsp", - argv = Seq( - millPath, - "--bsp", - "--disable-ticker", - "--color", - "false", - "--jobs", - s"${jobs}" - ) ++ (if (debug) Seq("--debug") else Seq()), - millVersion = MillBuildInfo.millVersion, - bspVersion = Constants.bspProtocolVersion, - languages = Constants.languages - ) - ) - } - - override def createBspConnection( - jobs: Int, - serverName: String - )(implicit ctx: Ctx): (PathRef, ujson.Value) = { - // we create a json connection file - val bspFile = ctx.workspace / Constants.bspDir / s"${serverName}.json" - if (os.exists(bspFile)) ctx.log.info(s"Overwriting BSP connection file: ${bspFile}") - else ctx.log.info(s"Creating BSP connection file: ${bspFile}") - val withDebug = ctx.log.debugEnabled - if (withDebug) ctx.log.debug( - "Enabled debug logging for the BSP server. If you want to disable it, you need to re-run this install command without the --debug option." - ) - val connectionContent = bspConnectionJson(jobs, withDebug) - os.write.over(bspFile, connectionContent, createFolders = true) - (PathRef(bspFile), upickle.default.read[ujson.Value](connectionContent)) - } +private class BspWorkerImpl() extends BspWorker { override def startBspServer( initialEvaluator: Option[Evaluator], streams: SystemStreams, logStream: PrintStream, logDir: os.Path, - canReload: Boolean, - serverHandles: Seq[Promise[BspServerHandle]] - ): BspServerResult = { + canReload: Boolean + ): Either[String, BspServerHandle] = { val evaluator = initialEvaluator.map(_.withFailFast(false)) val millServer = @@ -98,6 +48,7 @@ class BspWorkerImpl() extends BspWorker { )) .setExecutorService(executor) .create() + millServer.onConnectWithClient(launcher.getRemoteProxy) val listening = launcher.startListening() millServer.cancellator = shutdownBefore => { @@ -106,58 +57,52 @@ class BspWorkerImpl() extends BspWorker { } val bspServerHandle = new BspServerHandle { - private[this] var _lastResult: Option[BspServerResult] = None + private[this] var lastResult0: Option[BspServerResult] = None override def runSession(evaluator: Evaluator): BspServerResult = { - _lastResult = None + lastResult0 = None millServer.updateEvaluator(Option(evaluator)) val onReload = Promise[BspServerResult]() millServer.onSessionEnd = Some { serverResult => if (!onReload.isCompleted) { streams.err.println("Unsetting evaluator on session end") millServer.updateEvaluator(None) - _lastResult = Some(serverResult) + lastResult0 = Some(serverResult) onReload.success(serverResult) } } - Await.result(onReload.future, Duration.Inf).tap { r => - streams.err.println(s"Reload finished, result: ${r}") - _lastResult = Some(r) - } + val res = Await.result(onReload.future, Duration.Inf) + streams.err.println(s"Reload finished, result: ${res}") + lastResult0 = Some(res) + res } - override def lastResult: Option[BspServerResult] = _lastResult + override def lastResult: Option[BspServerResult] = lastResult0 override def stop(): Unit = { streams.err.println("Stopping server via handle...") listening.cancel(true) } } - serverHandles.foreach(_.success(bspServerHandle)) - listening.get() - () + new Thread(() => { + listening.get() + streams.err.println("Shutting down executor") + executor.shutdown() + }).start() + + Right(bspServerHandle) } catch { case _: CancellationException => - streams.err.println("The mill server was shut down.") + Left("The mill server was shut down.") case e: Exception => - streams.err.println( + Left( s"""An exception occurred while connecting to the client. |Cause: ${e.getCause} |Message: ${e.getMessage} |Exception class: ${e.getClass} - |Stack Trace: ${e.getStackTrace}""".stripMargin + |Stack Trace: ${e.getStackTrace.mkString("\n")}""".stripMargin ) - } finally { - streams.err.println("Shutting down executor") - executor.shutdown() - } - - val finalReuslt = - if (shutdownRequestedBeforeExit) BspServerResult.Shutdown - else BspServerResult.Failure - - finalReuslt } } diff --git a/bsp/worker/src/mill/bsp/worker/MillBspLogger.scala b/bsp/worker/src/mill/bsp/worker/MillBspLogger.scala index 412edb9e36f..ce6aa38d2cb 100644 --- a/bsp/worker/src/mill/bsp/worker/MillBspLogger.scala +++ b/bsp/worker/src/mill/bsp/worker/MillBspLogger.scala @@ -17,7 +17,7 @@ import mill.util.{ColorLogger, ProxyLogger} * @param logger the logger to which the messages received by this * MillBspLogger are being redirected */ -class MillBspLogger(client: BuildClient, taskId: Int, logger: Logger) +private class MillBspLogger(client: BuildClient, taskId: Int, logger: Logger) extends ProxyLogger(logger) with ColorLogger { def infoColor = fansi.Color.Blue diff --git a/bsp/worker/src/mill/bsp/worker/MillBuildServer.scala b/bsp/worker/src/mill/bsp/worker/MillBuildServer.scala index 60b9cae97da..59d1d0edabb 100644 --- a/bsp/worker/src/mill/bsp/worker/MillBuildServer.scala +++ b/bsp/worker/src/mill/bsp/worker/MillBuildServer.scala @@ -56,27 +56,24 @@ import ch.epfl.scala.bsp4j.{ import ch.epfl.scala.bsp4j import com.google.gson.JsonObject import mill.T -import mill.api.{DummyTestReporter, PathRef, Result, Strict, internal} +import mill.api.{DummyTestReporter, Result, Strict} import mill.define.Segment.Label -import mill.define.{Args, Discover, ExternalModule, Module, Segments, Task} +import mill.define.{Args, Discover, ExternalModule, Task} import mill.eval.Evaluator -import mill.main.{BspServerResult, MainModule} -import mill.scalalib.{GenIdeaImpl, JavaModule, SemanticDbJavaModule, TestModule} +import mill.main.MainModule +import mill.scalalib.{JavaModule, SemanticDbJavaModule, TestModule} import mill.scalalib.bsp.{BspModule, JvmBuildTarget, ScalaBuildTarget} -import mill.scalalib.internal.ModuleUtils -import mill.runner.{MillBuildBootstrap, MillBuildRootModule} -import os.{Path, root} +import mill.runner.MillBuildRootModule import java.io.PrintStream import java.util.concurrent.CompletableFuture import scala.concurrent.Promise import scala.jdk.CollectionConverters._ import scala.reflect.ClassTag -import scala.util.chaining.scalaUtilChainingOps import scala.util.{Failure, Success, Try} - -@internal -class MillBuildServer( +import Utils.sanitizeUri +import mill.bsp.BspServerResult +private class MillBuildServer( initialEvaluator: Option[Evaluator], bspVersion: String, serverVersion: String, @@ -97,104 +94,20 @@ class MillBuildServer( protected var clientWantsSemanticDb = false protected var clientIsIntelliJ = false - class State(val evaluator: Evaluator) { - private[this] object internal { - def init(): Unit = synchronized { - idToModule match { - case None => - val modules: Seq[(Module, Seq[Module])] = rootModules - .map(rootModule => (rootModule, ModuleUtils.transitiveModules(rootModule))) - - val map = modules - .flatMap { case (rootModule, otherModules) => - (Seq(rootModule) ++ otherModules).collect { - case m: BspModule => - val uri = sanitizeUri( - rootModule.millSourcePath / m.millModuleSegments.parts - ) - - val id = new BuildTargetIdentifier(uri) - - (id, m) - } - } - .toMap - idToModule = Some(map) - modulesToId = Some(map.map(_.swap)) - log.debug(s"BspModules: ${map.mapValues(_.bspDisplayName)}") - case _ => // already init - } - } - private[MillBuildServer] var idToModule: Option[Map[BuildTargetIdentifier, BspModule]] = None - private[MillBuildServer] var modulesToId: Option[Map[BspModule, BuildTargetIdentifier]] = None - } - - lazy val rootModules: Seq[mill.main.RootModule] = { - val evaluated = new mill.runner.MillBuildBootstrap( - projectRoot = evaluator.rootModule.millSourcePath, - home = os.home, - keepGoing = false, - imports = Nil, - env = Map.empty, - threadCount = None, - targetsAndParams = Seq("resolve", "_"), - prevRunnerState = mill.runner.RunnerState.empty, - logger = evaluator.baseLogger - ).evaluate() - - val rootModules0 = evaluated.result.frames - .flatMap(_.classLoaderOpt) - .zipWithIndex - .map { case (c, i) => - MillBuildBootstrap - .getRootModule(c, i, evaluator.rootModule.millSourcePath) - .fold(sys.error(_), identity(_)) - } - - val bootstrapModule = evaluated.result.bootstrapModuleOpt.map(m => - MillBuildBootstrap - .getChildRootModule( - m, - evaluated.result.frames.length, - evaluator.rootModule.millSourcePath - ) - .fold(sys.error(_), identity(_)) - ) - - rootModules0 ++ bootstrapModule - } - def bspModulesById: Map[BuildTargetIdentifier, BspModule] = { - internal.init() - internal.idToModule.get - } - def bspIdByModule: Map[BspModule, BuildTargetIdentifier] = { - internal.init() - internal.modulesToId.get - } - } - private[this] var statePromise: Promise[State] = Promise[State]() - initialEvaluator.foreach(e => statePromise.success(new State(e))) - + var evaluatorOpt: Option[Evaluator] = None + def evaluator = evaluatorOpt.get + updateEvaluator(initialEvaluator) def updateEvaluator(evaluator: Option[Evaluator]): Unit = { - log.debug(s"Updating Evaluator: ${evaluator}") - if (statePromise.isCompleted) { - // replace the promise - statePromise = Promise[State]() - } - evaluator.foreach(e => statePromise.success(new State(e))) + debug(s"Updating Evaluator: $evaluator") + if (statePromise.isCompleted) statePromise = Promise[State]() // replace the promise + evaluatorOpt = evaluator + evaluatorOpt.foreach(e => + statePromise.success(new State(e.rootModule.millSourcePath, e.baseLogger, debug)) + ) } - object log { - def debug(msg: String) = logStream.println(msg) - } - - object sanitizeUri { - def apply(uri: String): String = - if (uri.endsWith("/")) apply(uri.substring(0, uri.length - 1)) else uri - def apply(uri: os.Path): String = apply(uri.toNIO.toUri.toString) - def apply(uri: PathRef): String = apply(uri.path) - } + def debug(msg: String) = logStream.println(msg) override def onConnectWithClient(buildClient: BuildClient): Unit = client = buildClient @@ -204,24 +117,6 @@ class MillBuildServer( // TODO: scan BspModules and infer their capabilities - val clientCaps = request.getCapabilities().getLanguageIds().asScala - -// val compileLangs = moduleBspInfo.filter(_.canCompile).flatMap(_.languageIds).distinct.filter( -// clientCaps.contains -// ) -// val runLangs = -// moduleBspInfo.filter(_.canRun).flatMap( -// _.languageIds -// ).distinct // .filter(clientCaps.contains) -// val testLangs = -// moduleBspInfo.filter(_.canTest).flatMap( -// _.languageIds -// ).distinct //.filter(clientCaps.contains) -// val debugLangs = -// moduleBspInfo.filter(_.canDebug).flatMap( -// _.languageIds -// ).distinct //.filter(clientCaps.contains) - val supportedLangs = Seq("java", "scala").asJava val capabilities = new BuildServerCapabilities @@ -248,16 +143,16 @@ class MillBuildServer( val rawValue = json.get(name) if (rawValue.isJsonPrimitive) { val version = Try(rawValue.getAsJsonPrimitive.getAsString).toOption.filter(_.nonEmpty) - log.debug(s"Got json value for ${name}=${version}") + debug(s"Got json value for ${name}=${version}") version } else None } else None request.getData match { case d: JsonObject => - log.debug(s"extra data: ${d} of type ${d.getClass}") + debug(s"extra data: ${d} of type ${d.getClass}") readVersion(d, "semanticdbVersion").foreach { version => - log.debug( + debug( s"Got client semanticdbVersion: ${version}. Enabling SemanticDB support." ) clientWantsSemanticDb = true @@ -278,23 +173,23 @@ class MillBuildServer( } override def buildShutdown(): CompletableFuture[Object] = { - log.debug(s"Entered buildShutdown") + debug(s"Entered buildShutdown") shutdownRequested = true onSessionEnd match { case None => case Some(onEnd) => - log.debug("Shutdown build...") + debug("Shutdown build...") onEnd(BspServerResult.Shutdown) } SemanticDbJavaModule.resetContext() CompletableFuture.completedFuture(null.asInstanceOf[Object]) } override def onBuildExit(): Unit = { - log.debug("Entered onBuildExit") + debug("Entered onBuildExit") onSessionEnd match { case None => case Some(onEnd) => - log.debug("Exiting build...") + debug("Exiting build...") onEnd(BspServerResult.Shutdown) } SemanticDbJavaModule.resetContext() @@ -302,66 +197,58 @@ class MillBuildServer( } override def workspaceBuildTargets(): CompletableFuture[WorkspaceBuildTargetsResult] = - completable("workspaceBuildTargets") { state: State => - import state._ - targetTasks( - state, - targetIds = state.bspModulesById.keySet.toSeq, - agg = (items: Seq[BuildTarget]) => new WorkspaceBuildTargetsResult(items.asJava) - ) { - case (id, m) => - T.task { - val s = m.bspBuildTarget - val deps = m match { - case jm: JavaModule => - jm.recursiveModuleDeps.collect { - case bm: BspModule => bspIdByModule(bm) - } - case _ => Seq() - } - val data = m.bspBuildTargetData() match { - case Some((dataKind, d: ScalaBuildTarget)) => - Some(( - dataKind, - new bsp4j.ScalaBuildTarget( - d.scalaOrganization, - d.scalaVersion, - d.scalaBinaryVersion, - bsp4j.ScalaPlatform.forValue(d.platform.number), - d.jars.asJava - ) - )) - case Some((dataKind, d: JvmBuildTarget)) => - Some(( - dataKind, - new bsp4j.JvmBuildTarget( - d.javaHome.map(_.uri).getOrElse(null), - d.javaVersion.getOrElse(null) - ) - )) - case Some((dataKind, d)) => - // unsupported data kind - None - case None => None - } + completableTasks( + "workspaceBuildTargets", + targetIds = _.bspModulesById.keySet.toSeq, + tasks = { case m: JavaModule => T.task { m.bspBuildTargetData() } } + ) { (state, id, m, bspBuildTargetData) => + val s = m.bspBuildTarget + val deps = m match { + case jm: JavaModule => + jm.recursiveModuleDeps.collect { case bm: BspModule => state.bspIdByModule(bm) } + case _ => Seq() + } + val data = bspBuildTargetData match { + case Some((dataKind, d: ScalaBuildTarget)) => + val target = new bsp4j.ScalaBuildTarget( + d.scalaOrganization, + d.scalaVersion, + d.scalaBinaryVersion, + bsp4j.ScalaPlatform.forValue(d.platform.number), + d.jars.asJava + ) + Some((dataKind, target)) - new BuildTarget( - id, - s.tags.asJava, - s.languageIds.asJava, - deps.asJava, - new BuildTargetCapabilities(s.canCompile, s.canTest, s.canRun, s.canDebug) - ).tap { t => - s.displayName.foreach(t.setDisplayName) - s.baseDirectory.foreach(p => t.setBaseDirectory(sanitizeUri(p))) - data.foreach { d => - t.setDataKind(d._1) - t.setData(d._2) - } - } - } + case Some((dataKind, d: JvmBuildTarget)) => + val target = new bsp4j.JvmBuildTarget( + d.javaHome.map(_.uri).getOrElse(null), + d.javaVersion.getOrElse(null) + ) + Some((dataKind, target)) + + case Some((dataKind, d)) => None // unsupported data kind + case None => None } - } + + val buildTarget = new BuildTarget( + id, + s.tags.asJava, + s.languageIds.asJava, + deps.asJava, + new BuildTargetCapabilities(s.canCompile, s.canTest, s.canRun, s.canDebug) + ) + + s.displayName.foreach(buildTarget.setDisplayName) + s.baseDirectory.foreach(p => buildTarget.setBaseDirectory(sanitizeUri(p))) + + for ((dataKind, data) <- data) { + buildTarget.setDataKind(dataKind) + buildTarget.setData(data) + } + + buildTarget + + }(new WorkspaceBuildTargetsResult(_)) override def workspaceReload(): CompletableFuture[Object] = completableNoState("workspaceReload", false) { @@ -370,7 +257,7 @@ class MillBuildServer( onSessionEnd match { case None => "unsupportedWorkspaceReload".asInstanceOf[Object] case Some(onEnd) => - log.debug("Reloading workspace...") + debug("Reloading workspace...") onEnd(BspServerResult.ReloadWorkspace).asInstanceOf[Object] } } @@ -386,41 +273,38 @@ class MillBuildServer( ) } - completable(hint = s"buildTargetSources ${sourcesParams}") { state: State => - import state._ - targetTasks( - state, - targetIds = sourcesParams.getTargets.asScala.toSeq, - agg = (items: Seq[SourcesItem]) => new SourcesResult(items.asJava) - ) { - case (id, module: MillBuildRootModule) => + completableTasks( + hint = s"buildTargetSources ${sourcesParams}", + targetIds = _ => sourcesParams.getTargets.asScala.toSeq, + tasks = { + case module: MillBuildRootModule => T.task { - val items = - module.scriptSources().map(p => sourceItem(p.path, false)) ++ - module.sources().map(p => sourceItem(p.path, false)) ++ - module.generatedSources().map(p => sourceItem(p.path, true)) - new SourcesItem(id, items.asJava) + module.scriptSources().map(p => sourceItem(p.path, false)) ++ + module.sources().map(p => sourceItem(p.path, false)) ++ + module.generatedSources().map(p => sourceItem(p.path, true)) } - case (id, module: JavaModule) => + case module: JavaModule => T.task { - val items = module.sources().map(p => sourceItem(p.path, false)) ++ + module.sources().map(p => sourceItem(p.path, false)) ++ module.generatedSources().map(p => sourceItem(p.path, true)) - new SourcesItem(id, items.asJava) } } + ) { + case (state, id, module, items) => new SourcesItem(id, items.asJava) + } { + new SourcesResult(_) } + } override def buildTargetInverseSources(p: InverseSourcesParams) : CompletableFuture[InverseSourcesResult] = { completable(s"buildtargetInverseSources ${p}") { state => - import state._ - - val tasks = bspModulesById.iterator.collect { + val tasks = state.bspModulesById.iterator.collect { case (id, m: JavaModule) => T.task { val src = m.allSourceFiles() - val found = src.map(sanitizeUri.apply).contains( + val found = src.map(sanitizeUri).contains( p.getTextDocument.getUri ) if (found) Seq((id)) else Seq() @@ -437,30 +321,32 @@ class MillBuildServer( */ override def buildTargetDependencySources(p: DependencySourcesParams) : CompletableFuture[DependencySourcesResult] = - completable(hint = s"buildTargetDependencySources ${p}") { state: State => - targetTasks( - state, - targetIds = p.getTargets.asScala.toSeq, - agg = (items: Seq[DependencySourcesItem]) => new DependencySourcesResult(items.asJava) - ) { - case (id, m: JavaModule) => - val buildSources = - if (!m.isInstanceOf[MillBuildRootModule]) Nil - else mill.scalalib.Lib - .resolveMillBuildDeps(Nil, None, useSources = true) - .map(sanitizeUri(_)) - - T.task { - val sources = m.resolveDeps( + completableTasks( + hint = s"buildTargetDependencySources ${p}", + targetIds = _ => p.getTargets.asScala.toSeq, + tasks = { case m: JavaModule => + T.task { + ( + m.resolveDeps( T.task(m.transitiveCompileIvyDeps() ++ m.transitiveIvyDeps()), sources = true - )() - - val unmanaged = m.unmanagedClasspath() - val cp = (sources ++ unmanaged).map(sanitizeUri.apply).toSeq ++ buildSources - new DependencySourcesItem(id, cp.asJava) - } + )(), + m.unmanagedClasspath() + ) + } } + ) { + case (state, id, m: JavaModule, (resolveDepsSources, unmanagedClasspath)) => + val buildSources = + if (!m.isInstanceOf[MillBuildRootModule]) Nil + else mill.scalalib.Lib + .resolveMillBuildDeps(Nil, None, useSources = true) + .map(sanitizeUri(_)) + + val cp = (resolveDepsSources ++ unmanagedClasspath).map(sanitizeUri).toSeq ++ buildSources + new DependencySourcesItem(id, cp.asJava) + } { + new DependencySourcesResult(_) } /** @@ -470,47 +356,53 @@ class MillBuildServer( : CompletableFuture[DependencyModulesResult] = completableTasks( hint = "buildTargetDependencyModules", - targetIds = params.getTargets.asScala.toSeq, - agg = (items: Seq[DependencyModulesItem]) => new DependencyModulesResult(items.asJava) + targetIds = _ => params.getTargets.asScala.toSeq, + tasks = { case m: JavaModule => + T.task { (m.transitiveCompileIvyDeps(), m.transitiveIvyDeps(), m.unmanagedClasspath()) } + } ) { - case (id, m: JavaModule) => - T.task { - val ivy = m.transitiveCompileIvyDeps() ++ m.transitiveIvyDeps() - val deps = ivy.map { dep => - new DependencyModule(dep.dep.module.repr, dep.dep.version) - } - val unmanged = m.unmanagedClasspath().map { dep => - new DependencyModule(s"unmanaged-${dep.path.last}", "") - } - new DependencyModulesItem(id, (deps ++ unmanged).iterator.toSeq.asJava) + case ( + state, + id, + m: JavaModule, + (transitiveCompileIvyDeps, transitiveIvyDeps, unmanagedClasspath) + ) => + val ivy = transitiveCompileIvyDeps ++ transitiveIvyDeps + val deps = ivy.map { dep => + new DependencyModule(dep.dep.module.repr, dep.dep.version) + } + val unmanged = unmanagedClasspath.map { dep => + new DependencyModule(s"unmanaged-${dep.path.last}", "") } + new DependencyModulesItem(id, (deps ++ unmanged).iterator.toSeq.asJava) + } { + new DependencyModulesResult(_) } override def buildTargetResources(p: ResourcesParams): CompletableFuture[ResourcesResult] = completableTasks( s"buildTargetResources ${p}", - targetIds = p.getTargets.asScala.toSeq, - agg = (items: Seq[ResourcesItem]) => new ResourcesResult(items.asJava) + targetIds = _ => p.getTargets.asScala.toSeq, + tasks = { + case m: JavaModule => T.task { m.resources() } + case _ => T.task { Nil } + } ) { - case (id, m: JavaModule) => T.task { - val resources = m.resources().map(_.path).filter(os.exists).map(sanitizeUri.apply) - new ResourcesItem(id, resources.asJava) - } - case (id, _) => T.task { - // no java module, no resources - new ResourcesItem(id, Seq.empty[String].asJava) - } + case (state, id, m, resources) => + val resourcesUrls = resources.map(_.path).filter(os.exists).map(sanitizeUri) + new ResourcesItem(id, resourcesUrls.asJava) + + } { + new ResourcesResult(_) } // TODO: if the client wants to give compilation arguments and the module // already has some from the build file, what to do? override def buildTargetCompile(p: CompileParams): CompletableFuture[CompileResult] = completable(s"buildTargetCompile ${p}") { state => - import state._ - val params = TaskParameters.fromCompileParams(p) val taskId = params.hashCode() - val compileTasks = params.getTargets.distinct.map(bspModulesById).map { + val compileTasks = params.getTargets.distinct.map(state.bspModulesById).map { case m: SemanticDbJavaModule if clientWantsSemanticDb => m.compiledClassesAndSemanticDbFiles case m: JavaModule => m.compile case m => T.task { @@ -522,7 +414,7 @@ class MillBuildServer( val result = evaluator.evaluate( compileTasks, - Utils.getBspLoggedReporterPool(p.getOriginId, bspIdByModule, client), + Utils.getBspLoggedReporterPool(p.getOriginId, state.bspIdByModule, client), DummyTestReporter, new MillBspLogger(client, taskId, evaluator.baseLogger) ) @@ -534,23 +426,21 @@ class MillBuildServer( override def buildTargetOutputPaths(params: OutputPathsParams) : CompletableFuture[OutputPathsResult] = completable(s"buildTargetOutputPaths ${params}") { state => - import state._ - val outItems = new OutputPathItem( // Spec says, a directory must end with a forward slash - sanitizeUri.apply(evaluator.outPath) + "/", + sanitizeUri(evaluator.outPath) + "/", OutputPathItemKind.DIRECTORY ) val extItems = new OutputPathItem( // Spec says, a directory must end with a forward slash - sanitizeUri.apply(evaluator.externalOutPath) + "/", + sanitizeUri(evaluator.externalOutPath) + "/", OutputPathItemKind.DIRECTORY ) val items = for { target <- params.getTargets.asScala - module <- bspModulesById.get(target) + module <- state.bspModulesById.get(target) } yield { val items = if (module.millOuterCtx.external) List(extItems) @@ -563,17 +453,15 @@ class MillBuildServer( override def buildTargetRun(runParams: RunParams): CompletableFuture[RunResult] = completable(s"buildTargetRun ${runParams}") { state => - import state._ - val params = TaskParameters.fromRunParams(runParams) - val module = params.getTargets.map(bspModulesById).collectFirst { + val module = params.getTargets.map(state.bspModulesById).collectFirst { case m: JavaModule => m }.get val args = params.getArguments.getOrElse(Seq.empty[String]) val runTask = module.run(T.task(Args(args))) val runResult = evaluator.evaluate( Strict.Agg(runTask), - Utils.getBspLoggedReporterPool(runParams.getOriginId, bspIdByModule, client), + Utils.getBspLoggedReporterPool(runParams.getOriginId, state.bspIdByModule, client), logger = new MillBspLogger(client, runTask.hashCode(), evaluator.baseLogger) ) val response = runResult.results(runTask) match { @@ -590,10 +478,10 @@ class MillBuildServer( override def buildTargetTest(testParams: TestParams): CompletableFuture[TestResult] = completable(s"buildTargetTest ${testParams}") { state => - import state._ - - val modules = bspModulesById.values.toSeq.collect { case m: JavaModule => m } - val millBuildTargetIds = rootModules.map { case m: BspModule => bspIdByModule(m) }.toSet + val millBuildTargetIds = state + .rootModules + .map { case m: BspModule => state.bspIdByModule(m) } + .toSet val params = TaskParameters.fromTestParams(testParams) val argsMap = @@ -615,7 +503,7 @@ class MillBuildServer( val overallStatusCode = testParams.getTargets.asScala .filter(millBuildTargetIds.contains) .foldLeft(StatusCode.OK) { (overallStatusCode, targetId) => - bspModulesById(targetId) match { + state.bspModulesById(targetId) match { case testModule: TestModule => val testTask = testModule.testLocal(argsMap(targetId.getUri): _*) @@ -637,7 +525,11 @@ class MillBuildServer( val results = evaluator.evaluate( Strict.Agg(testTask), - Utils.getBspLoggedReporterPool(testParams.getOriginId, bspIdByModule, client), + Utils.getBspLoggedReporterPool( + testParams.getOriginId, + state.bspIdByModule, + client + ), testReporter, new MillBspLogger(client, testTask.hashCode, evaluator.baseLogger) ) @@ -677,21 +569,19 @@ class MillBuildServer( override def buildTargetCleanCache(cleanCacheParams: CleanCacheParams) : CompletableFuture[CleanCacheResult] = completable(s"buildTargetCleanCache ${cleanCacheParams}") { state => - import state._ - - val targetIds = rootModules.map { case b: BspModule => bspIdByModule(b) } + val targetIds = state.rootModules.map { case b: BspModule => state.bspIdByModule(b) } val (msg, cleaned) = cleanCacheParams.getTargets.asScala.filter(targetIds.contains).foldLeft(( "", true )) { case ((msg, cleaned), targetId) => - val module = bspModulesById(targetId) + val module = state.bspModulesById(targetId) val mainModule = new MainModule { override implicit def millDiscover: Discover[_] = Discover[this.type] } val compileTargetName = (module.millModuleSegments ++ Label("compile")).render - log.debug(s"about to clean: ${compileTargetName}") + debug(s"about to clean: ${compileTargetName}") val cleanTask = mainModule.clean(evaluator, Seq(compileTargetName): _*) val cleanResult = evaluator.evaluate( Strict.Agg(cleanTask), @@ -726,26 +616,20 @@ class MillBuildServer( throw new NotImplementedError("debugSessionStart endpoint is not implemented") } - def completableTasks[T: ClassTag, V]( + def completableTasks[T, V, W: ClassTag]( hint: String, - targetIds: Seq[BuildTargetIdentifier], - agg: Seq[T] => V - )(f: (BuildTargetIdentifier, BspModule) => Task[T]): CompletableFuture[V] = + targetIds: State => Seq[BuildTargetIdentifier], + tasks: BspModule => Task[W] + )(f: (State, BuildTargetIdentifier, BspModule, W) => T)(agg: java.util.List[T] => V) + : CompletableFuture[V] = completable(hint) { state: State => - targetTasks(state, targetIds, agg)(f) + val ids = targetIds(state) + val tasksSeq = ids.map(m => tasks(state.bspModulesById(m))) + val evaluated = evaluator.evalOrThrow()(tasksSeq) + val res = evaluated.zip(ids).map { case (v, i) => f(state, i, state.bspModulesById(i), v) } + agg(res.asJava) } - def targetTasks[T: ClassTag, V]( - state: State, - targetIds: Seq[BuildTargetIdentifier], - agg: Seq[T] => V - )(f: (BuildTargetIdentifier, BspModule) => Task[T]): V = { - import state._ - val tasks: Seq[Task[T]] = targetIds.distinct.map(id => f(id, bspModulesById(id))) - val res = evaluator.evalOrThrow()(tasks) - agg(res) - } - /** * Given a function that take input of type T and return output of type V, * apply the function on the given inputs and return a completable future of @@ -757,11 +641,11 @@ class MillBuildServer( hint: String, checkInitialized: Boolean = true )(f: State => V): CompletableFuture[V] = { - log.debug(s"Entered ${hint}") + debug(s"Entered ${hint}") val start = System.currentTimeMillis() val prefix = hint.split(" ").head def took = - log.debug(s"${prefix} took ${System.currentTimeMillis() - start} msec") + debug(s"${prefix} took ${System.currentTimeMillis() - start} msec") val future = new CompletableFuture[V]() @@ -777,7 +661,7 @@ class MillBuildServer( try { val v = f(state) took - log.debug(s"${prefix} result: ${v}") + debug(s"${prefix} result: ${v}") future.complete(v) } catch { case e: Exception => @@ -799,11 +683,11 @@ class MillBuildServer( hint: String, checkInitialized: Boolean = true )(f: => V): CompletableFuture[V] = { - log.debug(s"Entered ${hint}") + debug(s"Entered ${hint}") val start = System.currentTimeMillis() val prefix = hint.split(" ").head def took = - log.debug(s"${prefix} took ${System.currentTimeMillis() - start} msec") + debug(s"${prefix} took ${System.currentTimeMillis() - start} msec") val future = new CompletableFuture[V]() @@ -817,7 +701,7 @@ class MillBuildServer( try { val v = f took - log.debug(s"${prefix} result: ${v}") + debug(s"${prefix} result: ${v}") future.complete(v) } catch { case e: Exception => diff --git a/bsp/worker/src/mill/bsp/worker/MillJavaBuildServer.scala b/bsp/worker/src/mill/bsp/worker/MillJavaBuildServer.scala index f4858515ad3..455ba319f5b 100644 --- a/bsp/worker/src/mill/bsp/worker/MillJavaBuildServer.scala +++ b/bsp/worker/src/mill/bsp/worker/MillJavaBuildServer.scala @@ -7,42 +7,40 @@ import ch.epfl.scala.bsp4j.{ JavacOptionsResult } import mill.T -import mill.api.internal +import mill.bsp.worker.Utils.sanitizeUri import mill.scalalib.{JavaModule, SemanticDbJavaModule} import java.util.concurrent.CompletableFuture import scala.jdk.CollectionConverters._ -@internal -trait MillJavaBuildServer extends JavaBuildServer { this: MillBuildServer => +private trait MillJavaBuildServer extends JavaBuildServer { this: MillBuildServer => override def buildTargetJavacOptions(javacOptionsParams: JavacOptionsParams) : CompletableFuture[JavacOptionsResult] = - completable(s"buildTargetJavacOptions ${javacOptionsParams}") { state => - targetTasks( - state, - targetIds = javacOptionsParams.getTargets.asScala.toSeq, - agg = (items: Seq[JavacOptionsItem]) => new JavacOptionsResult(items.asJava) - ) { - case (id, m: JavaModule) => - val classesPathTask = m match { - case sem: SemanticDbJavaModule if clientWantsSemanticDb => - sem.bspCompiledClassesAndSemanticDbFiles - case _ => m.bspCompileClassesPath - } - - val pathResolver = state.evaluator.pathsResolver - T.task { - val options = m.javacOptions() - val classpath = - m.bspCompileClasspath().map(_.resolve(pathResolver)).map(sanitizeUri.apply) - new JavacOptionsItem( - id, - options.asJava, - classpath.iterator.toSeq.asJava, - sanitizeUri(classesPathTask().resolve(pathResolver)) - ) - } + completableTasks( + s"buildTargetJavacOptions ${javacOptionsParams}", + targetIds = _ => javacOptionsParams.getTargets.asScala.toSeq, + tasks = { case m: JavaModule => + val classesPathTask = m match { + case sem: SemanticDbJavaModule if clientWantsSemanticDb => + sem.bspCompiledClassesAndSemanticDbFiles + case _ => m.bspCompileClassesPath + } + T.task { (classesPathTask(), m.javacOptions(), m.bspCompileClasspath()) } } + ) { + case (state, id, m: JavaModule, (classesPath, javacOptions, bspCompileClasspath)) => + val pathResolver = evaluator.pathsResolver + val options = javacOptions + val classpath = + bspCompileClasspath.map(_.resolve(pathResolver)).map(sanitizeUri) + new JavacOptionsItem( + id, + options.asJava, + classpath.iterator.toSeq.asJava, + sanitizeUri(classesPath.resolve(pathResolver)) + ) + } { + new JavacOptionsResult(_) } } diff --git a/bsp/worker/src/mill/bsp/worker/MillJvmBuildServer.scala b/bsp/worker/src/mill/bsp/worker/MillJvmBuildServer.scala index 55aedac0dfc..925c7f9295d 100644 --- a/bsp/worker/src/mill/bsp/worker/MillJvmBuildServer.scala +++ b/bsp/worker/src/mill/bsp/worker/MillJvmBuildServer.scala @@ -11,55 +11,74 @@ import ch.epfl.scala.bsp4j.{ JvmTestEnvironmentResult } import mill.T -import mill.api.internal -import mill.define.Task +import mill.bsp.worker.Utils.sanitizeUri import mill.scalalib.JavaModule -import mill.scalalib.bsp.BspModule import java.util.concurrent.CompletableFuture import scala.jdk.CollectionConverters._ -import scala.util.chaining.scalaUtilChainingOps -@internal -trait MillJvmBuildServer extends JvmBuildServer { this: MillBuildServer => +private trait MillJvmBuildServer extends JvmBuildServer { this: MillBuildServer => override def jvmRunEnvironment(params: JvmRunEnvironmentParams) - : CompletableFuture[JvmRunEnvironmentResult] = - completable(s"jvmRunEnvironment ${params}") { state => - targetTasks( - state, - targetIds = params.getTargets.asScala.toSeq, - agg = (items: Seq[JvmEnvironmentItem]) => new JvmRunEnvironmentResult(items.asJava) - )(taskToJvmEnvironmentItem) - } + : CompletableFuture[JvmRunEnvironmentResult] = { + jvmRunTestEnvironment( + s"jvmRunEnvironment ${params}", + params.getTargets.asScala.toSeq, + new JvmRunEnvironmentResult(_) + ) + } override def jvmTestEnvironment(params: JvmTestEnvironmentParams) - : CompletableFuture[JvmTestEnvironmentResult] = - completable(s"jvmTestEnvironment ${params}") { state => - targetTasks( - state, - targetIds = params.getTargets.asScala.toSeq, - agg = (items: Seq[JvmEnvironmentItem]) => new JvmTestEnvironmentResult(items.asJava) - )(taskToJvmEnvironmentItem) - } + : CompletableFuture[JvmTestEnvironmentResult] = { + jvmRunTestEnvironment( + s"jvmTestEnvironment ${params}", + params.getTargets.asScala.toSeq, + new JvmTestEnvironmentResult(_) + ) + } - private val taskToJvmEnvironmentItem - : (BuildTargetIdentifier, BspModule) => Task[JvmEnvironmentItem] = { - case (id, m: JavaModule) => - T.task { - val classpath = m.runClasspath().map(_.path).map(sanitizeUri.apply) - new JvmEnvironmentItem( + def jvmRunTestEnvironment[V]( + name: String, + targetIds: Seq[BuildTargetIdentifier], + agg: java.util.List[JvmEnvironmentItem] => V + ) = { + completableTasks( + name, + targetIds = _ => targetIds, + tasks = { + case m: JavaModule => + T.task { + ( + m.runClasspath(), + m.forkArgs(), + m.forkWorkingDir(), + m.forkEnv(), + m.mainClass(), + m.zincWorker.worker(), + m.compile() + ) + } + } + ) { + case ( + state, + id, + m: JavaModule, + (runClasspath, forkArgs, forkWorkingDir, forkEnv, mainClass, zincWorker, compile) + ) => + val classpath = runClasspath.map(_.path).map(sanitizeUri) + val item = new JvmEnvironmentItem( id, classpath.iterator.toSeq.asJava, - m.forkArgs().asJava, - m.forkWorkingDir().toString(), - m.forkEnv().asJava - ).tap { item => - val classes = - m.mainClass().toList ++ m.zincWorker.worker().discoverMainClasses(m.compile()) - item.setMainClasses( - classes.map(new JvmMainClass(_, Nil.asJava)).asJava - ) - } - } + forkArgs.asJava, + forkWorkingDir.toString(), + forkEnv.asJava + ) + + val classes = mainClass.toList ++ zincWorker.discoverMainClasses(compile) + item.setMainClasses(classes.map(new JvmMainClass(_, Nil.asJava)).asJava) + item + } { + agg + } } } diff --git a/bsp/worker/src/mill/bsp/worker/MillScalaBuildServer.scala b/bsp/worker/src/mill/bsp/worker/MillScalaBuildServer.scala index ad69114c8ce..5eb83900476 100644 --- a/bsp/worker/src/mill/bsp/worker/MillScalaBuildServer.scala +++ b/bsp/worker/src/mill/bsp/worker/MillScalaBuildServer.scala @@ -14,7 +14,7 @@ import ch.epfl.scala.bsp4j.{ ScalacOptionsResult } import mill.{Agg, T} -import mill.api.internal +import mill.bsp.worker.Utils.sanitizeUri import mill.util.Jvm import mill.scalalib.{JavaModule, ScalaModule, SemanticDbJavaModule, TestModule} import mill.testrunner.TestRunner @@ -22,108 +22,107 @@ import sbt.testing.Fingerprint import java.util.concurrent.CompletableFuture import scala.jdk.CollectionConverters._ -import scala.util.chaining.scalaUtilChainingOps -@internal -trait MillScalaBuildServer extends ScalaBuildServer { this: MillBuildServer => +private trait MillScalaBuildServer extends ScalaBuildServer { this: MillBuildServer => override def buildTargetScalacOptions(p: ScalacOptionsParams) : CompletableFuture[ScalacOptionsResult] = - completable(hint = s"buildTargetScalacOptions ${p}") { state => - targetTasks( - state, - targetIds = p.getTargets.asScala.toSeq, - agg = (items: Seq[ScalacOptionsItem]) => new ScalacOptionsResult(items.asJava) - ) { - case (id, m: JavaModule) => - val optionsTask = m match { - case sm: ScalaModule => sm.allScalacOptions - case _ => T.task { Seq.empty[String] } - } + completableTasks( + hint = s"buildTargetScalacOptions ${p}", + targetIds = _ => p.getTargets.asScala.toSeq, + tasks = { + case m: ScalaModule => val classesPathTask = m match { case sem: SemanticDbJavaModule if clientWantsSemanticDb => sem.bspCompiledClassesAndSemanticDbFiles case _ => m.bspCompileClassesPath } - val pathResolver = state.evaluator.pathsResolver - T.task { - new ScalacOptionsItem( - id, - optionsTask().asJava, - m.bspCompileClasspath() - .iterator - .map(_.resolve(pathResolver)) - .map(sanitizeUri.apply).toSeq.asJava, - sanitizeUri(classesPathTask().resolve(pathResolver)) - ) + T.task((m.allScalacOptions(), m.bspCompileClasspath(), classesPathTask())) + + case m: JavaModule => + val classesPathTask = m match { + case sem: SemanticDbJavaModule if clientWantsSemanticDb => + sem.bspCompiledClassesAndSemanticDbFiles + case _ => m.bspCompileClassesPath } + T.task { (Nil, Nil, classesPathTask()) } } + ) { + case (state, id, m: JavaModule, (allScalacOptions, bspCompileClsaspath, classesPathTask)) => + val pathResolver = evaluator.pathsResolver + new ScalacOptionsItem( + id, + allScalacOptions.asJava, + bspCompileClsaspath.iterator + .map(_.resolve(pathResolver)) + .map(sanitizeUri).toSeq.asJava, + sanitizeUri(classesPathTask.resolve(pathResolver)) + ) + + } { + new ScalacOptionsResult(_) } override def buildTargetScalaMainClasses(p: ScalaMainClassesParams) : CompletableFuture[ScalaMainClassesResult] = completableTasks( hint = "buildTargetScalaMainClasses", - targetIds = p.getTargets.asScala.toSeq, - agg = (items: Seq[ScalaMainClassesItem]) => new ScalaMainClassesResult(items.asJava) + targetIds = _ => p.getTargets.asScala.toSeq, + tasks = { case m: JavaModule => + T.task((m.zincWorker.worker(), m.compile(), m.forkArgs(), m.forkEnv())) + } ) { - case (id, m: JavaModule) => - T.task { - // We find all main classes, although we could also find only the configured one - val mainClasses = m.zincWorker.worker().discoverMainClasses(m.compile()) - // val mainMain = m.mainClass().orElse(if(mainClasses.size == 1) mainClasses.headOption else None) - val jvmOpts = m.forkArgs() - val envs = m.forkEnv() - val items = mainClasses.map(mc => - new ScalaMainClass(mc, Seq().asJava, jvmOpts.asJava).tap { - _.setEnvironmentVariables(envs.map(e => s"${e._1}=${e._2}").toSeq.asJava) - } - ) - new ScalaMainClassesItem(id, items.asJava) - } - case (id, _) => T.task { - // no Java module, so no main classes - new ScalaMainClassesItem(id, Seq.empty[ScalaMainClass].asJava) + case (state, id, m: JavaModule, (worker, compile, forkArgs, forkEnv)) => + // We find all main classes, although we could also find only the configured one + val mainClasses = worker.discoverMainClasses(compile) + // val mainMain = m.mainClass().orElse(if(mainClasses.size == 1) mainClasses.headOption else None) + val items = mainClasses.map { mc => + val scalaMc = new ScalaMainClass(mc, Seq().asJava, forkArgs.asJava) + scalaMc.setEnvironmentVariables(forkEnv.map(e => s"${e._1}=${e._2}").toSeq.asJava) + scalaMc } + new ScalaMainClassesItem(id, items.asJava) + case (state, id, _, _) => // no Java module, so no main classes + new ScalaMainClassesItem(id, Seq.empty[ScalaMainClass].asJava) + } { + new ScalaMainClassesResult(_) } override def buildTargetScalaTestClasses(p: ScalaTestClassesParams) : CompletableFuture[ScalaTestClassesResult] = completableTasks( s"buildTargetScalaTestClasses ${p}", - targetIds = p.getTargets.asScala.toSeq, - agg = (items: Seq[ScalaTestClassesItem]) => new ScalaTestClassesResult(items.asJava) + targetIds = _ => p.getTargets.asScala.toSeq, + tasks = { case m: TestModule => + T.task((m.runClasspath(), m.testFramework(), m.compile())) + } ) { - case (id, m: TestModule) => T.task { - val classpath = m.runClasspath() - val testFramework = m.testFramework() - val compResult = m.compile() - - val (frameworkName, classFingerprint): (String, Agg[(Class[_], Fingerprint)]) = - Jvm.inprocess( - classpath.map(_.path), - classLoaderOverrideSbtTesting = true, - isolated = true, - closeContextClassLoaderWhenDone = false, - cl => { - val framework = TestRunner.framework(testFramework)(cl) - val discoveredTests = TestRunner.discoverTests( - cl, - framework, - Agg(compResult.classes.path) - ) - (framework.name(), discoveredTests) - } - ) - val classes = Seq.from(classFingerprint.map(classF => classF._1.getName.stripSuffix("$"))) - new ScalaTestClassesItem(id, classes.asJava, frameworkName) - } - case (id, _) => T.task { - // Not a test module, so no test classes - new ScalaTestClassesItem(id, Seq.empty[String].asJava) - } + case (state, id, m: TestModule, (classpath, testFramework, compResult)) => + val (frameworkName, classFingerprint): (String, Agg[(Class[_], Fingerprint)]) = + Jvm.inprocess( + classpath.map(_.path), + classLoaderOverrideSbtTesting = true, + isolated = true, + closeContextClassLoaderWhenDone = false, + cl => { + val framework = TestRunner.framework(testFramework)(cl) + val discoveredTests = TestRunner.discoverTests( + cl, + framework, + Agg(compResult.classes.path) + ) + (framework.name(), discoveredTests) + } + )(new mill.api.Ctx.Home { def home = os.home }) + val classes = Seq.from(classFingerprint.map(classF => classF._1.getName.stripSuffix("$"))) + new ScalaTestClassesItem(id, classes.asJava, frameworkName) + case (state, id, _, _) => + // Not a test module, so no test classes + new ScalaTestClassesItem(id, Seq.empty[String].asJava) + } { + new ScalaTestClassesResult(_) } } diff --git a/bsp/worker/src/mill/bsp/worker/State.scala b/bsp/worker/src/mill/bsp/worker/State.scala new file mode 100644 index 00000000000..8af8a2aa135 --- /dev/null +++ b/bsp/worker/src/mill/bsp/worker/State.scala @@ -0,0 +1,67 @@ +package mill.bsp.worker + +import ch.epfl.scala.bsp4j.BuildTargetIdentifier +import mill.runner.MillBuildBootstrap +import mill.scalalib.bsp.BspModule +import mill.scalalib.internal.ModuleUtils +import mill.define.Module +import mill.util.ColorLogger + +private class State(projectRoot: os.Path, baseLogger: ColorLogger, debug: String => Unit) { + lazy val bspModulesById: Map[BuildTargetIdentifier, BspModule] = { + val modules: Seq[(Module, Seq[Module])] = rootModules + .map(rootModule => (rootModule, ModuleUtils.transitiveModules(rootModule))) + + val map = modules + .flatMap { case (rootModule, otherModules) => + (Seq(rootModule) ++ otherModules).collect { + case m: BspModule => + val uri = Utils.sanitizeUri( + rootModule.millSourcePath / m.millModuleSegments.parts + ) + + (new BuildTargetIdentifier(uri), m) + } + } + .toMap + debug(s"BspModules: ${map.mapValues(_.bspDisplayName)}") + + map + } + + lazy val rootModules: Seq[mill.main.RootModule] = { + val evaluated = new mill.runner.MillBuildBootstrap( + projectRoot = projectRoot, + home = os.home, + keepGoing = false, + imports = Nil, + env = Map.empty, + threadCount = None, + targetsAndParams = Seq("resolve", "_"), + prevRunnerState = mill.runner.RunnerState.empty, + logger = baseLogger + ).evaluate() + + val rootModules0 = evaluated.result.frames + .flatMap(_.classLoaderOpt) + .zipWithIndex + .map { case (c, i) => + MillBuildBootstrap + .getRootModule(c, i, projectRoot) + .fold(sys.error(_), identity(_)) + } + + val bootstrapModule = evaluated.result.bootstrapModuleOpt.map(m => + MillBuildBootstrap + .getChildRootModule( + m, + evaluated.result.frames.length, + projectRoot + ) + .fold(sys.error(_), identity(_)) + ) + + rootModules0 ++ bootstrapModule + } + lazy val bspIdByModule: Map[BspModule, BuildTargetIdentifier] = bspModulesById.map(_.swap) +} diff --git a/bsp/worker/src/mill/bsp/worker/TaskParameters.scala b/bsp/worker/src/mill/bsp/worker/TaskParameters.scala index 1efea44b462..bd880ee6e39 100644 --- a/bsp/worker/src/mill/bsp/worker/TaskParameters.scala +++ b/bsp/worker/src/mill/bsp/worker/TaskParameters.scala @@ -10,7 +10,7 @@ import scala.jdk.CollectionConverters._ * arguments for the execution of the task, and an optional * origin id generated by the client. */ -trait Parameters { +private trait Parameters { def getTargets: List[BuildTargetIdentifier] def getArguments: Option[Seq[String]] @@ -18,7 +18,7 @@ trait Parameters { def getOriginId: Option[String] } -case class CParams(compileParams: CompileParams) extends Parameters { +private case class CParams(compileParams: CompileParams) extends Parameters { override def getTargets: List[BuildTargetIdentifier] = { compileParams.getTargets.asScala.toList @@ -42,7 +42,7 @@ case class CParams(compileParams: CompileParams) extends Parameters { } -case class RParams(runParams: RunParams) extends Parameters { +private case class RParams(runParams: RunParams) extends Parameters { override def getTargets: List[BuildTargetIdentifier] = { List(runParams.getTarget) @@ -66,7 +66,7 @@ case class RParams(runParams: RunParams) extends Parameters { } -case class TParams(testParams: TestParams) extends Parameters { +private case class TParams(testParams: TestParams) extends Parameters { override def getTargets: List[BuildTargetIdentifier] = { testParams.getTargets.asScala.toList @@ -89,7 +89,7 @@ case class TParams(testParams: TestParams) extends Parameters { } } -object TaskParameters { +private object TaskParameters { /** * Convert parameters specific to the compile request diff --git a/bsp/worker/src/mill/bsp/worker/Utils.scala b/bsp/worker/src/mill/bsp/worker/Utils.scala index b05c58ee9c8..9edec117254 100644 --- a/bsp/worker/src/mill/bsp/worker/Utils.scala +++ b/bsp/worker/src/mill/bsp/worker/Utils.scala @@ -1,13 +1,20 @@ package mill.bsp.worker import ch.epfl.scala.bsp4j.{BuildClient, BuildTargetIdentifier, StatusCode, TaskId} -import mill.api.CompileProblemReporter +import mill.api.{CompileProblemReporter, PathRef} import mill.api.Result.{Skipped, Success} import mill.eval.Evaluator import mill.scalalib.JavaModule import mill.scalalib.bsp.BspModule -object Utils { +private object Utils { + + def sanitizeUri(uri: String): String = + if (uri.endsWith("/")) sanitizeUri(uri.substring(0, uri.length - 1)) else uri + + def sanitizeUri(uri: os.Path): String = sanitizeUri(uri.toNIO.toUri.toString) + + def sanitizeUri(uri: PathRef): String = sanitizeUri(uri.path) // define the function that spawns compilation reporter for each module based on the // module's hash code TODO: find something more reliable than the hash code diff --git a/build.sc b/build.sc index 918ef0a31d7..3d9bad0f5ff 100644 --- a/build.sc +++ b/build.sc @@ -1133,25 +1133,12 @@ object bsp extends MillModule with BuildInfo { ) } - object worker extends MillInternalModule with BuildInfo { + object worker extends MillInternalModule { override def compileModuleDeps = Seq(bsp, scalalib, testrunner, runner) override def ivyDeps = Agg( Deps.bsp4j, Deps.sbtTestInterface ) - - def buildInfoPackageName = "mill.bsp.worker" - def buildInfoMembers = T { - val workerDep = worker.publishSelfDependency() - Seq( - BuildInfo.Value( - "bsp4jVersion", - Deps.bsp4j.dep.version, - "BSP4j version (BSP Protocol version)." - ), - BuildInfo.Value("millBspWorkerVersion", workerDep.version, "BSP worker dependency.") - ) - } } } diff --git a/main/src/mill/main/BspServerStarter.scala b/main/src/mill/main/BspServerStarter.scala deleted file mode 100644 index 81449709cc5..00000000000 --- a/main/src/mill/main/BspServerStarter.scala +++ /dev/null @@ -1,28 +0,0 @@ -package mill.main - -import mill.eval.Evaluator -import mill.api.SystemStreams - -import java.io.PrintStream -import scala.concurrent.Promise - -trait BspServerStarter { - def startBspServer( - initialEvaluator: Option[Evaluator], - streams: SystemStreams, - logStream: Option[PrintStream], - workspaceDir: os.Path, - ammoniteHomeDir: os.Path, - canReload: Boolean, - serverHandle: Option[Promise[BspServerHandle]] = None - ): BspServerResult -} - -object BspServerStarter { - def apply(): BspServerStarter = { - // We cannot link this class directly, as it would give us a circular dependency - val bspClass = getClass().getClassLoader.loadClass("mill.bsp.BspServerStarterImpl") - val method = bspClass.getMethod("get") - method.invoke(null).asInstanceOf[BspServerStarter] - } -} diff --git a/runner/src/mill/runner/BspContext.scala b/runner/src/mill/runner/BspContext.scala deleted file mode 100644 index e77eaedbd4d..00000000000 --- a/runner/src/mill/runner/BspContext.scala +++ /dev/null @@ -1,53 +0,0 @@ -package mill.runner - -import mill.api.internal -import mill.main.{BspServerHandle, BspServerResult, BspServerStarter} -import mill.api.SystemStreams - -import java.io.PrintStream -import java.util.concurrent.{Executors, TimeUnit} -import scala.concurrent.duration.Duration -import scala.concurrent.{Await, ExecutionContext, Future, Promise} -import scala.util.chaining.scalaUtilChainingOps -import scala.util.control.NonFatal - -@internal -class BspContext(streams: SystemStreams, bspLogStream: Option[PrintStream], home: os.Path) { - // BSP mode, run with a simple evaluator command to inject the evaluator - // The command returns when the server exists or the workspace should be reloaded - // if the `lastResult` is `ReloadWorkspace` we re-run the script in a loop - - // import scala.concurrent.ExecutionContext.Implicits._ - val serverThreadContext = - ExecutionContext.fromExecutor(Executors.newSingleThreadExecutor()) - - streams.err.println("Running in BSP mode with hardcoded startSession command") - - val bspServerHandle = Promise[BspServerHandle]() - - streams.err.println("Trying to load BSP server...") - val bspServerFuture = Future { - try { - BspServerStarter().startBspServer( - initialEvaluator = None, - streams = streams, - logStream = bspLogStream, - workspaceDir = os.pwd, - ammoniteHomeDir = home, - canReload = true, - serverHandle = Some(bspServerHandle) - ) - } catch { - case NonFatal(e) => - streams.err.println(s"Could not start BSP server. ${e.getMessage}") - e.printStackTrace(streams.err) - BspServerResult.Failure - } - }(serverThreadContext) - - val handle = Await.result(bspServerHandle.future, Duration(10, TimeUnit.SECONDS)).tap { _ => - streams.err.println("BSP server started") - } - - val millArgs = List("mill.bsp.BSP/startSession") -} diff --git a/runner/src/mill/runner/MillBuildRootModule.scala b/runner/src/mill/runner/MillBuildRootModule.scala index f4df962df5a..bd33ef96b8e 100644 --- a/runner/src/mill/runner/MillBuildRootModule.scala +++ b/runner/src/mill/runner/MillBuildRootModule.scala @@ -45,8 +45,11 @@ class MillBuildRootModule()(implicit T.task { if (sources == true) super.resolveDeps(deps, true)() else { - // We need to resolve the sources to make GenIdeaExtendedTests pass for - // some reason, but we don't need to actually return them (???) + // We need to resolve the sources to make GenIdeaExtendedTests pass, + // because those do not call `resolveDeps` explicitly for build file + // `import $ivy`s but instead rely on the deps that are resolved as + // part of the bootstrapping process. We thus need to make sure + // bootstrapping the rootModule ends up putting the sources on disk val unused = super.resolveDeps(deps, true)() super.resolveDeps(deps, false)() } diff --git a/runner/src/mill/runner/MillMain.scala b/runner/src/mill/runner/MillMain.scala index 6590d13c961..622fccd9d9f 100644 --- a/runner/src/mill/runner/MillMain.scala +++ b/runner/src/mill/runner/MillMain.scala @@ -7,8 +7,8 @@ import scala.jdk.CollectionConverters._ import scala.util.Properties import mill.java9rtexport.Export import mill.api.{DummyInputStream, internal} -import mill.main.BspServerResult import mill.api.SystemStreams +import mill.bsp.{BspContext, BspServerResult} import mill.util.{PrintLogger, Util} @internal @@ -189,8 +189,12 @@ object MillMain { val bspContext = if (bspMode) Some(new BspContext(streams, bspLog, config.home)) else None + + val bspCmd = "mill.bsp.BSP/startSession" val targetsAndParams = - bspContext.map(_.millArgs).getOrElse(config.leftoverArgs.value.toList) + bspContext + .map(_ => Seq(bspCmd)) + .getOrElse(config.leftoverArgs.value.toList) var repeatForBsp = true var loopRes: (Boolean, RunnerState) = (false, RunnerState.empty) @@ -221,18 +225,19 @@ object MillMain { ) bspContext.foreach { ctx => - repeatForBsp = ctx.handle.lastResult == Some(BspServerResult.ReloadWorkspace) + repeatForBsp = + BspContext.bspServerHandle.lastResult == Some(BspServerResult.ReloadWorkspace) logger.error( - s"`${ctx.millArgs.mkString(" ")}` returned with ${ctx.handle.lastResult}" + s"`$bspCmd` returned with ${BspContext.bspServerHandle.lastResult}" ) } loopRes = (isSuccess, evalStateOpt) } // while repeatForBsp bspContext.foreach { ctx => logger.error( - s"Exiting BSP runner loop. Stopping BSP server. Last result: ${ctx.handle.lastResult}" + s"Exiting BSP runner loop. Stopping BSP server. Last result: ${BspContext.bspServerHandle.lastResult}" ) - ctx.handle.stop() + BspContext.bspServerHandle.stop() } loopRes diff --git a/scalalib/api/src/mill/scalalib/api/ZincWorkerApi.scala b/scalalib/api/src/mill/scalalib/api/ZincWorkerApi.scala index 6390ef19665..130d7fa8146 100644 --- a/scalalib/api/src/mill/scalalib/api/ZincWorkerApi.scala +++ b/scalalib/api/src/mill/scalalib/api/ZincWorkerApi.scala @@ -33,9 +33,7 @@ trait ZincWorkerApi { reportCachedProblems: Boolean )(implicit ctx: ZincWorkerApi.Ctx): mill.api.Result[CompilationResult] - def discoverMainClasses(compilationResult: CompilationResult)(implicit - ctx: ZincWorkerApi.Ctx - ): Seq[String] + def discoverMainClasses(compilationResult: CompilationResult): Seq[String] def docJar( scalaVersion: String, diff --git a/scalalib/src/mill/scalalib/GenIdeaImpl.scala b/scalalib/src/mill/scalalib/GenIdeaImpl.scala index cb0575c7725..3da9c67745c 100755 --- a/scalalib/src/mill/scalalib/GenIdeaImpl.scala +++ b/scalalib/src/mill/scalalib/GenIdeaImpl.scala @@ -94,6 +94,7 @@ case class GenIdeaImpl( ) )(modules.map(_._2.repositoriesTask)) + Lib.resolveMillBuildDeps(moduleRepos.flatten, ctx, useSources = true) Lib.resolveMillBuildDeps(moduleRepos.flatten, ctx, useSources = false) } diff --git a/scalalib/worker/src/mill/scalalib/worker/ZincWorkerImpl.scala b/scalalib/worker/src/mill/scalalib/worker/ZincWorkerImpl.scala index b5aca5931e1..c9a3fe766b8 100644 --- a/scalalib/worker/src/mill/scalalib/worker/ZincWorkerImpl.scala +++ b/scalalib/worker/src/mill/scalalib/worker/ZincWorkerImpl.scala @@ -270,9 +270,7 @@ class ZincWorkerImpl( } - def discoverMainClasses(compilationResult: CompilationResult)(implicit - ctx: ZincWorkerApi.Ctx - ): Seq[String] = { + def discoverMainClasses(compilationResult: CompilationResult): Seq[String] = { def toScala[A](o: Optional[A]): Option[A] = if (o.isPresent) Some(o.get) else None toScala(FileAnalysisStore.binary(compilationResult.analysisFile.toIO).get())