Skip to content

Commit

Permalink
Merge pull request #858 from alexarchambault/bsp-workspace-reload
Browse files Browse the repository at this point in the history
Add support for BSP workspace/reload (reworked version of #852)
  • Loading branch information
alexarchambault authored Apr 7, 2022
2 parents 2ffc81f + d807a33 commit 95cec0a
Show file tree
Hide file tree
Showing 22 changed files with 655 additions and 303 deletions.
9 changes: 6 additions & 3 deletions build.sc
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,8 @@ object integration extends Module {
def forkEnv = super.forkEnv() ++ Seq(
"SCALA_CLI_TMP" -> tmpDirBase().path.toString,
"SCALA_CLI_IMAGE" -> "scala-cli",
"CI" -> "1"
"CI" -> "1",
"ACTUAL_CI" -> (if (System.getenv("CI") == null) "" else "1")
)
}
}
Expand All @@ -88,7 +89,8 @@ object integration extends Module {
def forkEnv = super.forkEnv() ++ Seq(
"SCALA_CLI_TMP" -> tmpDirBase().path.toString,
"SCALA_CLI_IMAGE" -> "scala-cli-slim",
"CI" -> "1"
"CI" -> "1",
"ACTUAL_CI" -> (if (System.getenv("CI") == null) "" else "1")
)
}
}
Expand Down Expand Up @@ -590,7 +592,8 @@ trait CliIntegrationBase extends SbtModule with ScalaCliPublishModule with HasTe
"SCALA_CLI" -> testLauncher().path.toString,
"SCALA_CLI_KIND" -> cliKind(),
"SCALA_CLI_TMP" -> tmpDirBase().path.toString,
"CI" -> "1"
"CI" -> "1",
"ACTUAL_CI" -> (if (System.getenv("CI") == null) "" else "1")
)
private def updateRef(name: String, ref: PathRef): PathRef = {
val rawPath = ref.path.toString.replace(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,12 @@ object EitherCps:
case Left(e) => throw EitherFailure(e, cps)
case Right(v) => v

def either[E, V](op: EitherCps[E] ?=> V): Either[E, V] =
val cps = new EitherCps[E]
try Right(op(using cps))
catch
case EitherFailure(e: E @unchecked, `cps`) =>
Left(e)
final case class Helper[E]():
def apply[V](op: EitherCps[E] ?=> V): Either[E, V] =
val cps = new EitherCps[E]
try Right(op(using cps))
catch
case EitherFailure(e: E @unchecked, `cps`) =>
Left(e)

def either[E]: Helper[E] = Helper[E]()
91 changes: 50 additions & 41 deletions modules/build/src/main/scala/scala/build/Inputs.scala
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,54 @@ object Inputs {
readArchive(Nil)
}

def validateArgs(
args: Seq[String],
cwd: os.Path,
download: String => Either[String, Array[Byte]],
stdinOpt: => Option[Array[Byte]],
acceptFds: Boolean
): Seq[Either[String, Seq[Element]]] = args.zipWithIndex.map {
case (arg, idx) =>
lazy val path = os.Path(arg, cwd)
lazy val dir = path / os.up
lazy val subPath = path.subRelativeTo(dir)
lazy val stdinOpt0 = stdinOpt
val isStdin = (arg == "-.scala" || arg == "_" || arg == "_.scala") &&
stdinOpt0.nonEmpty
if (isStdin) Right(Seq(VirtualScalaFile(stdinOpt0.get, "<stdin>")))
else if ((arg == "-" || arg == "-.sc" || arg == "_.sc") && stdinOpt0.nonEmpty)
Right(Seq(VirtualScript(stdinOpt0.get, "stdin", os.sub / "stdin.sc")))
else if (arg.endsWith(".zip") && os.exists(os.Path(arg, cwd))) {
val content = os.read.bytes(os.Path(arg, cwd))
Right(resolveZipArchive(content))
}
else if (arg.contains("://")) {
val url =
if (githubGistsArchiveRegex.findFirstMatchIn(arg).nonEmpty) s"$arg/download" else arg
download(url).map { content =>
if (githubGistsArchiveRegex.findFirstMatchIn(arg).nonEmpty)
resolveZipArchive(content)
else
List(resolve(url, content))
}
}
else if (arg.endsWith(".sc")) Right(Seq(Script(dir, subPath)))
else if (arg.endsWith(".scala")) Right(Seq(ScalaFile(dir, subPath)))
else if (arg.endsWith(".java")) Right(Seq(JavaFile(dir, subPath)))
else if (os.isDir(path)) Right(Seq(Directory(path)))
else if (acceptFds && arg.startsWith("/dev/fd/")) {
val content = os.read.bytes(os.Path(arg, cwd))
Right(Seq(VirtualScript(content, arg, os.sub / s"input-${idx + 1}.sc")))
}
else {
val msg =
if (os.exists(path))
s"$arg: unrecognized source type (expected .scala or .sc extension, or a directory)"
else s"$arg: not found"
Left(msg)
}
}

private def forNonEmptyArgs(
args: Seq[String],
cwd: os.Path,
Expand All @@ -330,47 +378,8 @@ object Inputs {
acceptFds: Boolean,
forcedWorkspace: Option[os.Path]
): Either[String, Inputs] = {
val validatedArgs = args.zipWithIndex.map {
case (arg, idx) =>
lazy val path = os.Path(arg, cwd)
lazy val dir = path / os.up
lazy val subPath = path.subRelativeTo(dir)
lazy val stdinOpt0 = stdinOpt
val isStdin = (arg == "-.scala" || arg == "_" || arg == "_.scala") &&
stdinOpt0.nonEmpty
if (isStdin) Right(Seq(VirtualScalaFile(stdinOpt0.get, "<stdin>")))
else if ((arg == "-" || arg == "-.sc" || arg == "_.sc") && stdinOpt0.nonEmpty)
Right(Seq(VirtualScript(stdinOpt0.get, "stdin", os.sub / "stdin.sc")))
else if (arg.endsWith(".zip") && os.exists(os.Path(arg, cwd))) {
val content = os.read.bytes(os.Path(arg, cwd))
Right(resolveZipArchive(content))
}
else if (arg.contains("://")) {
val url =
if (githubGistsArchiveRegex.findFirstMatchIn(arg).nonEmpty) s"$arg/download" else arg
download(url).map { content =>
if (githubGistsArchiveRegex.findFirstMatchIn(arg).nonEmpty)
resolveZipArchive(content)
else
List(resolve(url, content))
}
}
else if (arg.endsWith(".sc")) Right(Seq(Script(dir, subPath)))
else if (arg.endsWith(".scala")) Right(Seq(ScalaFile(dir, subPath)))
else if (arg.endsWith(".java")) Right(Seq(JavaFile(dir, subPath)))
else if (os.isDir(path)) Right(Seq(Directory(path)))
else if (acceptFds && arg.startsWith("/dev/fd/")) {
val content = os.read.bytes(os.Path(arg, cwd))
Right(Seq(VirtualScript(content, arg, os.sub / s"input-${idx + 1}.sc")))
}
else {
val msg =
if (os.exists(path))
s"$arg: unrecognized source type (expected .scala or .sc extension, or a directory)"
else s"$arg: not found"
Left(msg)
}
}
val validatedArgs: Seq[Either[String, Seq[Element]]] =
validateArgs(args, cwd, download, stdinOpt, acceptFds)
val invalid = validatedArgs.collect {
case Left(msg) => msg
}
Expand Down
70 changes: 70 additions & 0 deletions modules/build/src/main/scala/scala/build/bsp/BloopSession.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package scala.build.bsp

import com.swoval.files.PathWatchers

import java.util.concurrent.atomic.AtomicReference

import scala.build.compiler.BloopCompiler
import scala.build.{Build, Inputs}

final class BloopSession(
val inputs: Inputs,
val remoteServer: BloopCompiler,
val bspServer: BspServer,
val watcher: Build.Watcher
) {
def resetDiagnostics(localClient: BspClient): Unit =
for (targetId <- bspServer.targetIds)
inputs.flattened().foreach {
case f: Inputs.SingleFile =>
localClient.resetDiagnostics(f.path, targetId)
case _: Inputs.Virtual =>
}
def dispose(): Unit = {
watcher.dispose()
remoteServer.shutdown()
}

def registerWatchInputs(): Unit =
inputs.elements.foreach {
case elem: Inputs.OnDisk =>
val eventFilter: PathWatchers.Event => Boolean = { event =>
val newOrDeletedFile =
event.getKind == PathWatchers.Event.Kind.Create ||
event.getKind == PathWatchers.Event.Kind.Delete
lazy val p = os.Path(event.getTypedPath.getPath.toAbsolutePath)
lazy val relPath = p.relativeTo(elem.path)
lazy val isHidden = relPath.segments.exists(_.startsWith("."))
def isScalaFile = relPath.last.endsWith(".sc") || relPath.last.endsWith(".scala")
def isJavaFile = relPath.last.endsWith(".java")
newOrDeletedFile && !isHidden && (isScalaFile || isJavaFile)
}
val watcher0 = watcher.newWatcher()
watcher0.register(elem.path.toNIO, Int.MaxValue)
watcher0.addObserver {
Build.onChangeBufferedObserver { event =>
if (eventFilter(event))
watcher.schedule()
}
}
case _ =>
}
}

object BloopSession {

final class Reference {
private val ref = new AtomicReference[BloopSession](null)
def get(): BloopSession = {
val session = ref.get()
if (session == null)
sys.error("BSP server not initialized yet")
session
}
def getAndNullify(): Option[BloopSession] =
Option(ref.getAndSet(null))
def update(former: BloopSession, newer: BloopSession, ifError: String): Unit =
if (!ref.compareAndSet(former, newer))
sys.error(ifError)
}
}
6 changes: 3 additions & 3 deletions modules/build/src/main/scala/scala/build/bsp/Bsp.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@ import scala.build.{Inputs, Logger}
import scala.concurrent.Future

trait Bsp {
def run(): Future[Unit]
def run(initialInputs: Inputs): Future[Unit]
def shutdown(): Unit
}

object Bsp {
def create(
inputs: Inputs,
argsToInputs: Seq[String] => Either[String, Inputs],
buildOptions: BuildOptions,
logger: Logger,
bloopRifleConfig: BloopRifleConfig,
Expand All @@ -26,7 +26,7 @@ object Bsp {
new BspImpl(
logger,
bloopRifleConfig,
inputs,
argsToInputs,
buildOptions,
verbosity,
threads,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ class BspClient(
logger: Logger,
var forwardToOpt: Option[b.BuildClient] = None
) extends b.BuildClient with BuildClientForwardStubs with BloopBuildClient
with HasGeneratedSources {
with HasGeneratedSourcesImpl {

private def updatedPublishDiagnosticsParams(
params: b.PublishDiagnosticsParams,
Expand Down
Loading

0 comments on commit 95cec0a

Please sign in to comment.