Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix BSP regressions and improved resource file handling #2420

Merged
merged 24 commits into from
Apr 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 20 additions & 14 deletions bsp/src/mill/bsp/BSP.scala
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,6 @@ object BSP extends ExternalModule with CoursierModule with BspServerStarter {

private[this] val millServerHandle = Promise[BspServerHandle]()

private def bspWorkerIvyDeps: T[Agg[Dep]] = T {
Agg(Dep.parse(BuildInfo.millBspWorkerDep))
}

private def bspWorkerLibs: T[Agg[PathRef]] = T {
mill.modules.Util.millProjectModule(
"MILL_BSP_WORKER",
Expand All @@ -45,15 +41,17 @@ object BSP extends ExternalModule with CoursierModule with BspServerStarter {
* reason, the message and stacktrace of the exception will be
* printed to stdout.
*/
def install(jobs: Int = 1): Command[Unit] = T.command {
def install(jobs: Int = 1): Command[(PathRef, ujson.Value)] = T.command {
// we create a file containing the additional jars to load
val cpFile = T.workspace / Constants.bspDir / s"${Constants.serverName}.resources"
val libUrls = bspWorkerLibs().map(_.path.toNIO.toUri.toURL).iterator.toSeq
val cpFile =
T.workspace / Constants.bspDir / s"${Constants.serverName}-${mill.BuildInfo.millVersion}.resources"
os.write.over(
cpFile,
bspWorkerLibs().iterator.map(_.path.toNIO.toUri.toURL).mkString("\n"),
libUrls.mkString("\n"),
createFolders = true
)
BspWorker(T.ctx()).map(_.createBspConnection(jobs, Constants.serverName))
BspWorker(T.ctx(), Some(libUrls)).map(_.createBspConnection(jobs, Constants.serverName))
}

/**
Expand All @@ -73,23 +71,22 @@ object BSP extends ExternalModule with CoursierModule with BspServerStarter {
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(
streams.err,
streams.err,
DummyInputStream
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)
Expand All @@ -106,11 +103,20 @@ object BSP extends ExternalModule with CoursierModule with BspServerStarter {
worker.startBspServer(
initialEvaluator,
streams,
logStream.getOrElse(streams.err),
workspaceDir / Constants.bspDir,
canReload,
Seq(millServerHandle) ++ serverHandle.toSeq
)
case _ => BspServerResult.Failure
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
}
}

Expand Down
36 changes: 25 additions & 11 deletions bsp/src/mill/bsp/BspWorker.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import mill.eval.Evaluator
import mill.main.{BspServerHandle, BspServerResult}
import mill.api.SystemStreams

import java.io.{InputStream, PrintStream}
import java.io.PrintStream
import java.net.URL
import scala.concurrent.Promise
import scala.util.{Failure, Success, Try}
Expand All @@ -18,11 +18,12 @@ trait BspWorker {
def createBspConnection(
jobs: Int,
serverName: String
)(implicit ctx: Ctx): Unit
)(implicit ctx: Ctx): (PathRef, ujson.Value)

def startBspServer(
initialEvaluator: Option[Evaluator],
streams: SystemStreams,
logStream: PrintStream,
logDir: os.Path,
canReload: Boolean,
serverHandles: Seq[Promise[BspServerHandle]]
Expand All @@ -35,23 +36,36 @@ object BspWorker {

private[this] var worker: Option[BspWorker] = None

def apply(millCtx: Ctx.Workspace with Ctx.Home with Ctx.Log): Result[BspWorker] = {
def apply(
millCtx: Ctx.Workspace with Ctx.Home with Ctx.Log,
workerLibs: Option[Seq[URL]] = None
): Result[BspWorker] = {
worker match {
case Some(x) => Result.Success(x)
case None =>
// load extra classpath entries from file
val cpFile = millCtx.workspace / Constants.bspDir / s"${Constants.serverName}.resources"
if (!os.exists(cpFile)) return Result.Failure(
"You need to run `mill mill.bsp.BSP/install` before you can use the BSP server"
)
val urls = workerLibs.map { urls =>
millCtx.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(
"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
// TODO: if outdated, we could regenerate the resource file and re-load the worker

val urls = os.read(cpFile).linesIterator.map(u => new URL(u)).toSeq
// read the classpath from resource file
millCtx.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)
val workerVersion = Try {

// check the worker version
Try {
val workerBuildInfo = cl.loadClass(Constants.bspWorkerBuildInfoClass)
workerBuildInfo.getMethod("millBspWorkerVersion").invoke(null)
} match {
Expand Down
9 changes: 6 additions & 3 deletions bsp/worker/src/mill/bsp/worker/BspWorkerImpl.scala
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ class BspWorkerImpl() extends BspWorker {
override def createBspConnection(
jobs: Int,
serverName: String
)(implicit ctx: Ctx): Unit = {
)(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}")
Expand All @@ -58,12 +58,15 @@ class BspWorkerImpl() extends BspWorker {
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."
)
os.write.over(bspFile, bspConnectionJson(jobs, withDebug), createFolders = true)
val connectionContent = bspConnectionJson(jobs, withDebug)
os.write.over(bspFile, connectionContent, createFolders = true)
(PathRef(bspFile), upickle.default.read[ujson.Value](connectionContent))
}

override def startBspServer(
initialEvaluator: Option[Evaluator],
streams: SystemStreams,
logStream: PrintStream,
logDir: os.Path,
canReload: Boolean,
serverHandles: Seq[Promise[BspServerHandle]]
Expand All @@ -76,7 +79,7 @@ class BspWorkerImpl() extends BspWorker {
bspVersion = Constants.bspProtocolVersion,
serverVersion = MillBuildInfo.millVersion,
serverName = Constants.serverName,
logStream = streams.err,
logStream = logStream,
canReload = canReload
) with MillJvmBuildServer with MillJavaBuildServer with MillScalaBuildServer

Expand Down
3 changes: 1 addition & 2 deletions build.sc
Original file line number Diff line number Diff line change
Expand Up @@ -1097,8 +1097,7 @@ object bsp extends MillModule with BuildInfo{
def buildInfoMembers = T{
val workerDep = worker.publishSelfDependency()
Seq(
BuildInfo.Value("bsp4jVersion", Deps.bsp4j.dep.version, "BSP4j version (BSP Protocol version)."),
BuildInfo.Value("millBspWorkerDep", s"${workerDep.group}:${workerDep.id}:${workerDep.version}", "BSP worker dependency.")
BuildInfo.Value("bsp4jVersion", Deps.bsp4j.dep.version, "BSP4j version (BSP Protocol version).")
)
}

Expand Down
28 changes: 16 additions & 12 deletions contrib/buildinfo/src/mill/contrib/buildinfo/BuildInfo.scala
Original file line number Diff line number Diff line change
Expand Up @@ -165,12 +165,16 @@ object BuildInfo {
|package ${buildInfoPackageName}
|
|object $buildInfoObjectName {
| private val buildInfoProperties = new java.util.Properties
| private[this] val buildInfoProperties: java.util.Properties = new java.util.Properties()
|
| private val buildInfoInputStream = getClass
| .getResourceAsStream("$buildInfoObjectName.buildinfo.properties")
| private[this] val buildInfoInputStream = getClass
| .getResourceAsStream("${buildInfoObjectName}.buildinfo.properties")
|
| buildInfoProperties.load(buildInfoInputStream)
| try {
| buildInfoProperties.load(buildInfoInputStream)
| } finally {
| buildInfoInputStream.close()
| }
|
| $bindingsCode
|}
Expand All @@ -180,21 +184,21 @@ object BuildInfo {
|package ${buildInfoPackageName};
|
|public class $buildInfoObjectName {
| private static java.util.Properties buildInfoProperties = new java.util.Properties();
| private static final java.util.Properties buildInfoProperties = new java.util.Properties();
|
| static {
| java.io.InputStream buildInfoInputStream = $buildInfoObjectName
| java.io.InputStream buildInfoInputStream = ${buildInfoObjectName}
| .class
| .getResourceAsStream("$buildInfoObjectName.buildinfo.properties");
| .getResourceAsStream("${buildInfoObjectName}.buildinfo.properties");
|
| try{
| try {
| buildInfoProperties.load(buildInfoInputStream);
| }catch(java.io.IOException e){
| } catch (java.io.IOException e) {
| throw new RuntimeException(e);
| }finally{
| try{
| } finally {
| try {
| buildInfoInputStream.close();
| }catch(java.io.IOException e){
| } catch (java.io.IOException e) {
| throw new RuntimeException(e);
| }
| }
Expand Down
6 changes: 5 additions & 1 deletion main/api/src/mill/api/SystemStreams.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,8 @@ package mill.api

import java.io.{InputStream, PrintStream}

class SystemStreams(val out: PrintStream, val err: PrintStream, val in: InputStream)
class SystemStreams(
val out: PrintStream,
val err: PrintStream,
val in: InputStream
)
3 changes: 2 additions & 1 deletion main/src/mill/main/BspServerStarter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@ package mill.main
import mill.eval.Evaluator
import mill.api.SystemStreams

import java.io.{InputStream, PrintStream}
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,
Expand Down
6 changes: 3 additions & 3 deletions main/src/mill/modules/Util.scala
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package mill.modules

import coursier.Repository
import mill.BuildInfo
import mill.api.{Ctx, IO, Loose, PathRef}
import mill.{Agg, BuildInfo}
import mill.api.{Ctx, IO, Loose, PathRef, Result}

object Util {

Expand Down Expand Up @@ -70,7 +70,7 @@ object Util {
resolveFilter: os.Path => Boolean = _ => true,
// this should correspond to the mill runtime Scala version
artifactSuffix: String = "_2.13"
) = {
): Result[Agg[PathRef]] = {
millProperty(key) match {
case Some(localPath) =>
mill.api.Result.Success(
Expand Down
8 changes: 5 additions & 3 deletions runner/src/mill/runner/BspContext.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@ import mill.api.internal
import mill.main.{BspServerHandle, BspServerResult, BspServerStarter}
import mill.api.SystemStreams

import java.util.concurrent.Executors
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, home: os.Path) {
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
Expand All @@ -30,6 +31,7 @@ class BspContext(streams: SystemStreams, home: os.Path) {
BspServerStarter().startBspServer(
initialEvaluator = None,
streams = streams,
logStream = bspLogStream,
workspaceDir = os.pwd,
ammoniteHomeDir = home,
canReload = true,
Expand All @@ -43,7 +45,7 @@ class BspContext(streams: SystemStreams, home: os.Path) {
}
}(serverThreadContext)

val handle = Await.result(bspServerHandle.future, Duration.Inf).tap { _ =>
val handle = Await.result(bspServerHandle.future, Duration(10, TimeUnit.SECONDS)).tap { _ =>
streams.err.println("BSP server started")
}

Expand Down
Loading