Skip to content

Fix BSP workspace/reload to always pick up dependencies from using directives #1681

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

Merged
merged 2 commits into from
Dec 13, 2022
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
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import scala.build.input.{Inputs, OnDisk, SingleFile, Virtual}

final class BloopSession(
val inputs: Inputs,
val inputsHash: String,
val remoteServer: BloopCompiler,
val bspServer: BspServer,
val watcher: Build.Watcher
Expand Down Expand Up @@ -54,6 +55,13 @@ final class BloopSession(

object BloopSession {

def apply(
inputs: Inputs,
remoteServer: BloopCompiler,
bspServer: BspServer,
watcher: Build.Watcher
): BloopSession = new BloopSession(inputs, inputs.sourceHash(), remoteServer, bspServer, watcher)

final class Reference {
private val ref = new AtomicReference[BloopSession](null)
def get(): BloopSession = {
Expand Down
12 changes: 5 additions & 7 deletions modules/build/src/main/scala/scala/build/bsp/BspImpl.scala
Original file line number Diff line number Diff line change
Expand Up @@ -390,12 +390,7 @@ final class BspImpl(
build(bloopSession0, actualLocalClient, notifyChanges = true, reloadableOptions),
()
)
lazy val bloopSession0: BloopSession = new BloopSession(
inputs,
remoteServer,
bspServer,
watcher
)
lazy val bloopSession0: BloopSession = BloopSession(inputs, remoteServer, bspServer, watcher)

bloopSession0.registerWatchInputs()
bspServer.newInputs(inputs)
Expand Down Expand Up @@ -578,8 +573,11 @@ final class BspImpl(
}
}
val newInputs = value(argsToInputs(ideInputs.args))
val newHash = newInputs.sourceHash()
val previousInputs = currentBloopSession.inputs
if (newInputs == previousInputs) CompletableFuture.completedFuture(new Object)
val previousHash = currentBloopSession.inputsHash
if newInputs == previousInputs && newHash == previousHash then
CompletableFuture.completedFuture(new Object)
else reloadBsp(currentBloopSession, previousInputs, newInputs, reloadableOptions)
}
maybeResponse match {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,11 +79,12 @@ trait BuildServerForwardStubs extends b.BuildServer {
forwardTo.workspaceBuildTargets()
.handle(fatalExceptionHandler("workspaceBuildTargets"))

/** This implementation should never be called and is merely a placeholder. As Bloop doesn't
* support reloading its workspace, Scala CLI has to reload Bloop instead. And so,
* [[BuildServerProxy]].workspaceReload() is responsible for the actual reload.
*/
override def workspaceReload(): CompletableFuture[Object] =
CompletableFuture.completedFuture(new Object)
// Bloop does not support workspaceReload and Intellij calls it at the start
// forwardTo.workspaceReload()
// .handle(fatalExceptionHandler("workspaceReload"))
CompletableFuture.completedFuture(new Object) // should never be called, as per scaladoc

override def buildTargetDependencyModules(params: DependencyModulesParams)
: CompletableFuture[DependencyModulesResult] =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ import scala.build.GeneratedSource
import scala.build.input.Inputs
import scala.build.options.Scope

/** A wrapper for [[BspServer]], allowing to reload the workspace on the fly.
* @param bspServer
* the underlying BSP server relying on Bloop
* @param onReload
* the actual `workspace/reload` function
*/
class BuildServerProxy(
bspServer: () => BspServer,
onReload: () => CompletableFuture[Object]
Expand Down Expand Up @@ -77,6 +83,10 @@ class BuildServerProxy(
override def buildTargetOutputPaths(params: b.OutputPathsParams)
: CompletableFuture[b.OutputPathsResult] =
bspServer().buildTargetOutputPaths(params)

/** As Bloop doesn't support `workspace/reload` requests and we have to reload it on Scala CLI's
* end, this is used instead of [[BspServer]]'s [[BspServerForwardStubs]].workspaceReload().
*/
override def workspaceReload(): CompletableFuture[AnyRef] =
onReload()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -977,6 +977,59 @@ abstract class BspTestDefinitions(val scalaVersionOpt: Option[String])
}
}

test("workspace/reload extra dependency directive") {
val sourceFilePath = os.rel / "ReloadTest.scala"
val inputs = TestInputs(
sourceFilePath ->
s"""object ReloadTest {
| println(os.pwd)
|}
|""".stripMargin
)
inputs.fromRoot { root =>
os.proc(TestUtil.cli, "setup-ide", ".", extraOptions)
.call(
cwd = root,
stdout = os.Inherit
)
val ideOptionsPath = root / Constants.workspaceDirName / "ide-options-v2.json"
val jsonOptions = List("--json-options", ideOptionsPath.toString)
withBsp(inputs, Seq("."), bspOptions = jsonOptions, reuseRoot = Some(root)) {
(_, _, remoteServer) =>
async {
val buildTargetsResp = await(remoteServer.workspaceBuildTargets().asScala)
val targets = buildTargetsResp.getTargets.asScala.map(_.getId).toSeq

val resp =
await(remoteServer.buildTargetCompile(new b.CompileParams(targets.asJava)).asScala)
expect(resp.getStatusCode == b.StatusCode.ERROR)

val depName = "os-lib"
val depVersion = "0.8.1"
val updatedSourceFile =
s"""//> using lib "com.lihaoyi::$depName:$depVersion"
|
|object ReloadTest {
| println(os.pwd)
|}
|""".stripMargin
os.write.over(root / sourceFilePath, updatedSourceFile)

val reloadResponse =
extractWorkspaceReloadResponse(await(remoteServer.workspaceReload().asScala))
expect(reloadResponse.isEmpty)

val depSourcesParams = new b.DependencySourcesParams(targets.asJava)
val depSourcesResponse =
await(remoteServer.buildTargetDependencySources(depSourcesParams).asScala)
val depSources = depSourcesResponse.getItems.asScala.flatMap(_.getSources.asScala)
expect(depSources.exists(s => s.contains(depName) && s.contains(depVersion)))
}
}

}
}

test("workspace/reload of an extra sources directory") {
val dir1 = "dir1"
val dir2 = "dir2"
Expand Down