Skip to content

Commit

Permalink
Add --link flag to compile to allow linking through BSP
Browse files Browse the repository at this point in the history
Before this commit linking could only be done through the run command with BSP.
Because the BSP protocol does describe a linking command (as it is often a part
of the compile step) the flag --link has been added to the compile command. This
is useful for those kinds of projects that need the linker to be run to produce
source code for further toolchains, e.g. a ScalaJS project might often use
@JSExport and then run the code from some other JS entrypoint.
  • Loading branch information
KristianAN committed Dec 12, 2024
1 parent e188696 commit ab375ff
Show file tree
Hide file tree
Showing 3 changed files with 74 additions and 9 deletions.
60 changes: 57 additions & 3 deletions frontend/src/main/scala/bloop/bsp/BloopBspServices.scala
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,9 @@ import monix.execution.Scheduler
import monix.execution.atomic.AtomicBoolean
import monix.execution.atomic.AtomicInt
import monix.reactive.subjects.BehaviorSubject
import bloop.data.Platform.Js
import bloop.data.Platform.Jvm
import bloop.data.Platform.Native

final class BloopBspServices(
callSiteState: State,
Expand Down Expand Up @@ -421,6 +424,48 @@ final class BloopBspServices(
Task.eval { originToCompileStores.remove(params.originId); () }.executeAsync
}

def linkProjects(
userProjects: ProjectMapping,
state: State,
compileArgs: List[String],
originId: Option[String],
logger: BspServerLogger
): BspResult[bsp.CompileResult] = {
import bloop.engine.tasks.LinkTask.{linkJS, linkMainWithNative}
userProjects._2.platform match {
case platform @ Js(config, _, _) =>
val cmd = Commands.Link(List(userProjects._2.name))
val targetDir = ScalaJsToolchain.linkTargetFrom(userProjects._2, config)
linkJS(cmd, userProjects._2, state, None, targetDir, platform).map { state =>
val result = if (state.status == ExitStatus.LinkingError) {
Right(bsp.CompileResult(originId, bsp.StatusCode.Error, None, None))
} else {
Right(bsp.CompileResult(originId, bsp.StatusCode.Ok, None, None))
}
(state, result)
}
case Jvm(_, _, _, _, _, _) =>
compileProjects(userProjects :: Nil, state, compileArgs, originId, logger)
case platform @ Native(config, _, userMainClass) =>
userMainClass match {
case None =>
logger.error("No main class defined in project")
Task.now((state, Right(bsp.CompileResult(originId, bsp.StatusCode.Error, None, None))))
case Some(value) =>
val cmd = Commands.Link(List(userProjects._2.name))
val target = ScalaNativeToolchain.linkTargetFrom(userProjects._2, config)
linkMainWithNative(cmd, userProjects._2, state, value, target, platform).map { state =>
val result = if (state.status == ExitStatus.LinkingError) {
Right(bsp.CompileResult(originId, bsp.StatusCode.Error, None, None))
} else {
Right(bsp.CompileResult(originId, bsp.StatusCode.Ok, None, None))
}
(state, result)
}
}
}
}

def compileProjects(
userProjects: Seq[ProjectMapping],
state: State,
Expand Down Expand Up @@ -547,8 +592,17 @@ final class BloopBspServices(
case Right(mappings) =>
val compileArgs = params.arguments.getOrElse(Nil)
val isVerbose = compileArgs.exists(_ == "--verbose")
val isLink = compileArgs.exists(_ == "--link")
val logger = if (isVerbose) logger0.asBspServerVerbose else logger0
compileProjects(mappings, state, compileArgs, params.originId, logger)
(isLink, mappings.size) match {
case (false, _) =>
compileProjects(mappings, state, compileArgs, params.originId, logger)
case (true, 1) =>
linkProjects(mappings.head, state, compileArgs, params.originId, logger)
case (true, _) =>
logger0.error("Can only link one project")
Task.now((state, Right(bsp.CompileResult(None, bsp.StatusCode.Error, None, None))))
}
}
}
}
Expand Down Expand Up @@ -910,7 +964,7 @@ final class BloopBspServices(
project: Project,
state: State
): Task[State] = {
import bloop.engine.tasks.LinkTask.{linkMainWithJs, linkMainWithNative}
import bloop.engine.tasks.LinkTask.{linkJS, linkMainWithNative}
val cwd = state.commonOptions.workingPath

parseMainClass(project, state) match {
Expand Down Expand Up @@ -945,7 +999,7 @@ final class BloopBspServices(
case platform @ Platform.Js(config, _, _) =>
val cmd = Commands.Run(List(project.name))
val targetDir = ScalaJsToolchain.linkTargetFrom(project, config)
linkMainWithJs(cmd, project, state, mainClass.className, targetDir, platform)
linkJS(cmd, project, state, Some(mainClass.className), targetDir, platform)
.flatMap { state =>
val files = targetDir.list.map(_.toString())
// We use node to run the program (is this a special case?)
Expand Down
16 changes: 13 additions & 3 deletions frontend/src/main/scala/bloop/engine/Interpreter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ import bloop.reporter.ReporterInputs
import bloop.task.Task
import bloop.testing.LoggingEventHandler
import bloop.testing.TestInternals
import bloop.data.Platform.Js
import bloop.data.Platform.Jvm
import bloop.data.Platform.Native

object Interpreter {
// This is stack-safe because of Monix's trampolined execution
Expand Down Expand Up @@ -516,7 +519,14 @@ object Interpreter {
def doLink(project: Project)(state: State): Task[State] = {
compileAnd(cmd, state, List(project), cmd.cliOptions.noColor, "`link`") { state =>
getMainClass(state, project, cmd.main) match {
case Left(state) => Task.now(state)
case Left(state) =>
project.platform match {
case platform @ Platform.Js(config, _, _) =>
val targetDirectory = ScalaJsToolchain.linkTargetFrom(project, config)
LinkTask.linkJS(cmd, project, state, None, targetDirectory, platform)
case _ => Task.now(state)
}

case Right(mainClass) =>
project.platform match {
case platform @ Platform.Native(config, _, _) =>
Expand All @@ -525,7 +535,7 @@ object Interpreter {

case platform @ Platform.Js(config, _, _) =>
val targetDirectory = ScalaJsToolchain.linkTargetFrom(project, config)
LinkTask.linkMainWithJs(cmd, project, state, mainClass, targetDirectory, platform)
LinkTask.linkJS(cmd, project, state, Some(mainClass), targetDirectory, platform)

case _: Platform.Jvm =>
val msg = Feedback.noLinkFor(project)
Expand Down Expand Up @@ -570,7 +580,7 @@ object Interpreter {
case platform @ Platform.Js(config, _, _) =>
val targetDirectory = ScalaJsToolchain.linkTargetFrom(project, config)
LinkTask
.linkMainWithJs(cmd, project, state, mainClass, targetDirectory, platform)
.linkJS(cmd, project, state, Some(mainClass), targetDirectory, platform)
.flatMap { state =>
// We use node to run the program (is this a special case?)
val files = targetDirectory.list.map(_.toString())
Expand Down
7 changes: 4 additions & 3 deletions frontend/src/main/scala/bloop/engine/tasks/LinkTask.scala
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,12 @@ import bloop.io.AbsolutePath
import bloop.task.Task

object LinkTask {
def linkMainWithJs(

def linkJS(
cmd: LinkingCommand,
project: Project,
state: State,
mainClass: String,
mainClass: Option[String],
targetDirectory: AbsolutePath,
platform: Platform.Js
): Task[State] = {
Expand All @@ -41,7 +42,7 @@ object LinkTask {
project,
fullClasspath,
true,
Some(mainClass),
mainClass,
targetDirectory,
s,
logger
Expand Down

0 comments on commit ab375ff

Please sign in to comment.