Skip to content

Commit

Permalink
improvement: add custom bsp as possible build tool
Browse files Browse the repository at this point in the history
  • Loading branch information
kasiaMarek committed Nov 22, 2023
1 parent 57eb284 commit 34a05ba
Show file tree
Hide file tree
Showing 19 changed files with 265 additions and 172 deletions.
23 changes: 13 additions & 10 deletions metals/src/main/scala/scala/meta/internal/bsp/BspConnector.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ import scala.concurrent.ExecutionContext
import scala.concurrent.Future

import scala.meta.internal.bsp.BspConfigGenerationStatus._
import scala.meta.internal.builds.BloopInstallProvider
import scala.meta.internal.builds.BuildServerProvider
import scala.meta.internal.builds.BuildTool
import scala.meta.internal.builds.BuildTools
import scala.meta.internal.builds.SbtBuildTool
import scala.meta.internal.builds.ShellRunner
Expand Down Expand Up @@ -43,13 +45,9 @@ class BspConnector(
* Resolves the current build servers that either have a bsp entry or if the
* workspace can support Bloop, it will also resolve Bloop.
*/
def resolve(): BspResolvedResult = {
def resolve(buildTool: Option[BuildTool]): BspResolvedResult = {
resolveExplicit().getOrElse {
if (
buildTools
.loadSupported()
.exists(_.isBloopDefaultBsp) || buildTools.isBloop
)
if (buildTool.isInstanceOf[BloopInstallProvider] || buildTools.isBloop)
ResolvedBloop
else bspServers.resolve()
}
Expand All @@ -74,19 +72,20 @@ class BspConnector(
* of the bsp entry has already happened at this point.
*/
def connect(
projectRoot: AbsolutePath,
buildTool: Option[BuildTool],
workspace: AbsolutePath,
userConfiguration: UserConfiguration,
shellRunner: ShellRunner,
)(implicit ec: ExecutionContext): Future[Option[BspSession]] = {
val projectRoot = buildTool.map(_.projectRoot).getOrElse(workspace)
def connect(
projectRoot: AbsolutePath,
bspTraceRoot: AbsolutePath,
addLivenessMonitor: Boolean,
): Future[Option[BuildServerConnection]] = {
def bspStatusOpt = Option.when(addLivenessMonitor)(bspStatus)
scribe.info("Attempting to connect to the build server...")
resolve() match {
resolve(buildTool) match {
case ResolvedNone =>
scribe.info("No build server found")
Future.successful(None)
Expand Down Expand Up @@ -180,7 +179,9 @@ class BspConnector(
possibleBuildServerConn match {
case None => Future.successful(None)
case Some(buildServerConn)
if buildServerConn.isBloop && buildTools.isSbt =>
if buildServerConn.isBloop && buildTool.exists(
_.isInstanceOf[SbtBuildTool]
) =>
// NOTE: (ckipp01) we special case this here since sbt bsp server
// doesn't yet support metabuilds. So in the future when that
// changes, re-work this and move the creation of this out above
Expand Down Expand Up @@ -277,7 +278,9 @@ class BspConnector(
BspConnectionDetails,
]] = {
if (
bloopPresent || buildTools.loadSupported().exists(_.isBloopDefaultBsp)
bloopPresent || buildTools
.loadSupported()
.exists(_.isInstanceOf[BloopInstallProvider])
)
new BspConnectionDetails(
BloopServers.name,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ final class BloopInstall(
override def toString: String = s"BloopInstall($workspace)"

def runUnconditionally(
buildTool: BuildTool,
buildTool: BloopInstallProvider,
isImportInProcess: AtomicBoolean,
): Future[WorkspaceLoadedStatus] = {
if (isImportInProcess.compareAndSet(false, true)) {
Expand Down Expand Up @@ -121,7 +121,7 @@ final class BloopInstall(
// notifications. This method is synchronized to prevent asking the user
// twice whether to import the build.
def runIfApproved(
buildTool: BuildTool,
buildTool: BloopInstallProvider,
digest: String,
isImportInProcess: AtomicBoolean,
): Future[WorkspaceLoadedStatus] =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import scala.meta.io.AbsolutePath
/**
* Helper trait for build tools that have a Bloop plugin
*/
trait BloopInstallProvider { this: BuildTool =>
trait BloopInstallProvider extends BuildTool {

/**
* Method used to generate the necesary .bloop files for the
Expand Down
14 changes: 14 additions & 0 deletions metals/src/main/scala/scala/meta/internal/builds/BspOnly.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package scala.meta.internal.builds

import scala.meta.io.AbsolutePath

/**
* Build tool for custom bsp detected in `.bsp/<name>.json`
*/
case class BspOnly(
override val executableName: String,
override val projectRoot: AbsolutePath,
) extends BuildTool {
override def digest(workspace: AbsolutePath): Option[String] = Some("OK")
override val forcesBuildServer = true
}
37 changes: 14 additions & 23 deletions metals/src/main/scala/scala/meta/internal/builds/BuildTool.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,32 +4,12 @@ import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.StandardCopyOption

import scala.concurrent.Future

import scala.meta.io.AbsolutePath

trait BuildTool {

/**
* Export the build to Bloop
*
* This operation should be roughly equivalent to running `sbt bloopInstall`
* and should work for both updating an existing Bloop build or creating a new
* Bloop build.
*/
def bloopInstall(
workspace: AbsolutePath,
systemProcess: List[String] => Future[WorkspaceLoadedStatus],
): Future[WorkspaceLoadedStatus]

def digest(workspace: AbsolutePath): Option[String]

def version: String

def minimumVersion: String

def recommendedVersion: String

protected lazy val tempDir: Path = {
val dir = Files.createTempDirectory("metals")
dir.toFile.deleteOnExit()
Expand All @@ -40,15 +20,14 @@ trait BuildTool {

def executableName: String

def isBloopDefaultBsp = true

def projectRoot: AbsolutePath

val forcesBuildServer = false

}

object BuildTool {

case class Found(buildTool: BuildTool, digest: String)
def copyFromResource(
tempDir: Path,
filePath: String,
Expand All @@ -62,4 +41,16 @@ object BuildTool {
outFile
}

trait Verified
case class IncompatibleVersion(buildTool: VersionRecommendation)
extends Verified {
def message: String = s"Unsupported $buildTool version ${buildTool.version}"
}
case class NoChecksum(buildTool: BuildTool, root: AbsolutePath)
extends Verified {
def message: String =
s"Could not calculate checksum for ${buildTool.executableName} in $root"
}
case class Found(buildTool: BuildTool, digest: String) extends Verified

}
23 changes: 23 additions & 0 deletions metals/src/main/scala/scala/meta/internal/builds/BuildTools.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import scala.meta.internal.io.PathIO
import scala.meta.internal.metals.BloopServers
import scala.meta.internal.metals.MetalsEnrichments._
import scala.meta.internal.metals.UserConfiguration
import scala.meta.internal.metals.scalacli.ScalaCli
import scala.meta.io.AbsolutePath

import ujson.ParsingFailedException
Expand Down Expand Up @@ -121,6 +122,26 @@ final class BuildTools(
)
def isBazel: Boolean = bazelProject.isDefined

private def customBsps: List[BspOnly] = {
val bspFolder = workspace.resolve(".bsp")
val root = customProjectRoot.getOrElse(workspace)
if (bspFolder.exists && bspFolder.isDirectory) {
bspFolder.toFile
.listFiles()
.collect {
case file
if file.isFile() && file.getName().endsWith(".json") && !knowBsps(
file.getName().stripSuffix(".json")
) =>
BspOnly(file.getName().stripSuffix(".json"), root)
}
.toList
} else Nil
}

private def knowBsps =
Set(SbtBuildTool.name, MillBuildTool.name) ++ ScalaCli.names

private def customProjectRoot =
userConfig().customProjectRoot
.map(relativePath => workspace.resolve(relativePath.trim()))
Expand Down Expand Up @@ -187,6 +208,7 @@ final class BuildTools(
mavenProject.foreach(buf += MavenBuildTool(userConfig, _))
millProject.foreach(buf += MillBuildTool(userConfig, _))
scalaCliProject.foreach(buf += ScalaCliBuildTool(workspace, _, userConfig))
buf.addAll(customBsps)

buf.result()
}
Expand Down Expand Up @@ -221,6 +243,7 @@ final class BuildTools(
val before = lastDetectedBuildTools.getAndUpdate(_ + buildTool)
!before.contains(buildTool)
}

}

object BuildTools {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ case class GradleBuildTool(
userConfig: () => UserConfiguration,
projectRoot: AbsolutePath,
) extends BuildTool
with BloopInstallProvider {
with BloopInstallProvider
with VersionRecommendation {

private val initScriptName = "init-script.gradle"
private val gradleBloopVersion = BuildInfo.gradleBloopVersion
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ case class MavenBuildTool(
userConfig: () => UserConfiguration,
projectRoot: AbsolutePath,
) extends BuildTool
with BloopInstallProvider {
with BloopInstallProvider
with VersionRecommendation {

private lazy val embeddedMavenLauncher: AbsolutePath = {
val out = BuildTool.copyFromResource(tempDir, "maven-wrapper.jar")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ case class MillBuildTool(
projectRoot: AbsolutePath,
) extends BuildTool
with BloopInstallProvider
with BuildServerProvider {
with BuildServerProvider
with VersionRecommendation {

private def getMillVersion(workspace: AbsolutePath): String = {
import scala.meta.internal.jdk.CollectionConverters._
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ case class SbtBuildTool(
userConfig: () => UserConfiguration,
) extends BuildTool
with BloopInstallProvider
with BuildServerProvider {
with BuildServerProvider
with VersionRecommendation {

import SbtBuildTool._

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ class ScalaCliBuildTool(
val projectRoot: AbsolutePath,
userConfig: () => UserConfiguration,
) extends BuildTool
with BuildServerProvider {
with BuildServerProvider
with VersionRecommendation {

lazy val runScalaCliCommand: Option[Seq[String]] =
ScalaCli.localScalaCli(userConfig())
Expand Down Expand Up @@ -47,12 +48,6 @@ class ScalaCliBuildTool(
)
)

override def bloopInstall(
workspace: AbsolutePath,
systemProcess: List[String] => Future[WorkspaceLoadedStatus],
): Future[WorkspaceLoadedStatus] =
Future.successful(WorkspaceLoadedStatus.Dismissed)

override def digest(workspace: AbsolutePath): Option[String] =
ScalaCliDigest.current(workspace)

Expand All @@ -64,16 +59,19 @@ class ScalaCliBuildTool(

override def executableName: String = ScalaCliBuildTool.name

override def isBloopDefaultBsp = false
override val forcesBuildServer = true

def isBspGenerated(workspace: AbsolutePath): Boolean =
ScalaCliBuildTool.pathsToScalaCliBsp(workspace).exists(_.isFile)

}

object ScalaCliBuildTool {
def name = "scala-cli"
def pathsToScalaCliBsp(root: AbsolutePath): List[AbsolutePath] = List(
root.resolve(".bsp").resolve("scala-cli.json"),
root.resolve(".bsp").resolve("scala.json"),
)
def pathsToScalaCliBsp(root: AbsolutePath): List[AbsolutePath] =
ScalaCli.names.toList.map(name =>
root.resolve(".bsp").resolve(s"$name.json")
)

def apply(
workspace: AbsolutePath,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package scala.meta.internal.builds

trait VersionRecommendation { self: BuildTool =>
def minimumVersion: String
def recommendedVersion: String
def version: String
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import java.nio.file.Path
import scala.collection.mutable

import scala.meta.internal.builds.BuildTool
import scala.meta.internal.builds.VersionRecommendation
import scala.meta.internal.jdk.CollectionConverters._
import scala.meta.internal.metals.BloopJsonUpdateCause.BloopJsonUpdateCause
import scala.meta.internal.metals.clients.language.MetalsInputBoxParams
Expand Down Expand Up @@ -193,13 +194,13 @@ object Messages {
}

object ChooseBuildTool {
def message =
"Multiple build definitions found. Which would you like to use?"
def params(builtTools: List[BuildTool]): ShowMessageRequestParams = {
val messageActionItems =
builtTools.map(bt => new MessageActionItem(bt.executableName))
val params = new ShowMessageRequestParams()
params.setMessage(
"Multiple build definitions found. Which would you like to use?"
)
params.setMessage(message)
params.setType(MessageType.Info)
params.setActions(messageActionItems.asJava)
params
Expand Down Expand Up @@ -288,7 +289,7 @@ object Messages {

def learnMoreUrl: String = Urls.docs("import-build")

def params(tool: BuildTool): ShowMessageRequestParams = {
def params(tool: VersionRecommendation): ShowMessageRequestParams = {
def toFixMessage =
s"To fix this problem, upgrade to $tool ${tool.recommendedVersion} "

Expand Down
Loading

0 comments on commit 34a05ba

Please sign in to comment.