Skip to content

Commit

Permalink
refactor: extract connecting to build server (#6597)
Browse files Browse the repository at this point in the history
* refactor: extract connecting to build server

* fix build server promise + other tests fixes

* refector: get rid off connect providers

* extract fields for indexing into a trait
  • Loading branch information
kasiaMarek committed Aug 8, 2024
1 parent e56266a commit 0032e23
Show file tree
Hide file tree
Showing 16 changed files with 1,059 additions and 1,019 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -67,24 +67,7 @@ final class BspConfigGenerator(
case status => status
}

/**
* Given multiple build tools that are all BuildServerProviders, allow the
* choose the desired build server and then connect to it.
*/
def chooseAndGenerate(
buildTools: List[BuildServerProvider]
): Future[(BuildServerProvider, BspConfigGenerationStatus)] = {
for {
Some(buildTool) <- chooseBuildServerProvider(buildTools)
status <- buildTool.generateBspConfig(
workspace,
args => runUnconditionally(buildTool, args),
statusBar,
)
} yield (buildTool, status)
}

private def chooseBuildServerProvider(
def chooseBuildServerProvider(
buildTools: List[BuildServerProvider]
): Future[Option[BuildServerProvider]] = {
languageClient
Expand Down
86 changes: 17 additions & 69 deletions metals/src/main/scala/scala/meta/internal/bsp/BspConnector.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import java.nio.file.Files
import scala.concurrent.ExecutionContext
import scala.concurrent.Future

import scala.meta.internal.bsp.BspConfigGenerationStatus._
import scala.meta.internal.builds.BuildServerProvider
import scala.meta.internal.builds.BuildTool
import scala.meta.internal.builds.BuildTools
Expand All @@ -14,9 +13,13 @@ import scala.meta.internal.builds.ScalaCliBuildTool
import scala.meta.internal.builds.ShellRunner
import scala.meta.internal.metals.BloopServers
import scala.meta.internal.metals.BuildServerConnection
import scala.meta.internal.metals.ConnectKind
import scala.meta.internal.metals.CreateSession
import scala.meta.internal.metals.GenerateBspConfigAndConnect
import scala.meta.internal.metals.Messages
import scala.meta.internal.metals.Messages.BspSwitch
import scala.meta.internal.metals.MetalsEnrichments._
import scala.meta.internal.metals.SlowConnect
import scala.meta.internal.metals.StatusBar
import scala.meta.internal.metals.Tables
import scala.meta.internal.metals.UserConfiguration
Expand All @@ -40,7 +43,7 @@ class BspConnector(
workDoneProgress: WorkDoneProgress,
bspConfigGenerator: BspConfigGenerator,
currentConnection: () => Option[BuildServerConnection],
restartBspServer: () => Future[Boolean],
restartBspServer: () => Future[Unit],
bspStatus: ConnectionBspStatus,
)(implicit ec: ExecutionContext) {

Expand Down Expand Up @@ -111,7 +114,7 @@ class BspConnector(
val shouldReload = SbtBuildTool.writeSbtMetalsPlugins(projectRoot)
def restartSbtBuildServer() = currentConnection()
.withFilter(_.isSbt)
.map(_ => restartBspServer().ignoreValue)
.map(_ => restartBspServer())
.getOrElse(Future.successful(()))
val connectionF =
for {
Expand Down Expand Up @@ -152,7 +155,6 @@ class BspConnector(
args => bspConfigGenerator.runUnconditionally(bsp, args),
statusBar,
)
.map(status => handleGenerationStatus(bsp, status))
.flatMap { _ =>
connect(
projectRoot,
Expand All @@ -161,7 +163,6 @@ class BspConnector(
regeneratedConfig = true,
)
}

case _ =>
bspServers
.newServer(projectRoot, bspTraceRoot, details, bspStatusOpt)
Expand Down Expand Up @@ -299,10 +300,7 @@ class BspConnector(
* and connect to it, but stores that you want to change it unless you are
* choosing Bloop, since in that case it's special cased and does start it.
*/
def switchBuildServer(
workspace: AbsolutePath,
createBloopAndConnect: () => Future[BuildChange],
): Future[Boolean] = {
def switchBuildServer[T](): Future[Option[ConnectKind]] = {

val foundServers = bspServers.findAvailableServers()
val bloopPresent: Boolean = buildTools.isBloop
Expand Down Expand Up @@ -356,93 +354,43 @@ class BspConnector(
possibleChoice match {
case Some(choice) =>
allPossibleServers(choice) match {
case Left(buildTool) =>
buildTool
.generateBspConfig(
workspace,
args =>
bspConfigGenerator.runUnconditionally(buildTool, args),
statusBar,
)
.map(status => handleGenerationStatus(buildTool, status))
case Left(buildTool) => Some(GenerateBspConfigAndConnect(buildTool))
case Right(details) if details.getName == BloopServers.name =>
tables.buildServers.chooseServer(details.getName)
if (bloopPresent) {
Future.successful(true)
} else {
createBloopAndConnect().ignoreValue
Future.successful(false)
}
if (bloopPresent) Some(CreateSession())
else Some(SlowConnect)
case Right(details)
if !currentSelectedServer.contains(details.getName) =>
tables.buildServers.chooseServer(details.getName)
Future.successful(true)
case _ => Future.successful(false)
Some(CreateSession())
case _ => None
}
case _ =>
Future.successful(false)
case _ => None
}
}

allPossibleServers.keys.toList match {
case Nil =>
client.showMessage(BspSwitch.noInstalledServer)
Future.successful(false)
Future.successful(None)
case singleServer :: Nil =>
allPossibleServers(singleServer) match {
case Left(buildTool) =>
buildTool
.generateBspConfig(
workspace,
args => bspConfigGenerator.runUnconditionally(buildTool, args),
statusBar,
)
.map(status => handleGenerationStatus(buildTool, status))
Future.successful(Some(GenerateBspConfigAndConnect(buildTool)))
case Right(connectionDetails) =>
client.showMessage(
BspSwitch.onlyOneServer(name = connectionDetails.getName())
)
Future.successful(false)
Future.successful(None)
}
case multipleServers =>
val currentSelectedServer =
tables.buildServers
.selectedServer()
.orElse(currentConnection().map(_.name))
askUser(multipleServers, currentSelectedServer).flatMap(choice =>
askUser(multipleServers, currentSelectedServer).map(choice =>
handleServerChoice(choice, currentSelectedServer)
)
}
}

/**
* Handles showing the user what they need to know after an attempt to
* generate a bsp config has happened.
*/
private def handleGenerationStatus(
buildTool: BuildServerProvider,
status: BspConfigGenerationStatus,
): Boolean = status match {
case BspConfigGenerationStatus.Generated =>
tables.buildServers.chooseServer(buildTool.buildServerName)
true
case Cancelled => false
case Failed(exit) =>
exit match {
case Left(exitCode) =>
scribe.error(
s"Creation of .bsp/${buildTool.buildServerName} failed with exit code: $exitCode"
)
client.showMessage(
Messages.BspProvider.genericUnableToCreateConfig
)
case Right(message) =>
client.showMessage(
Messages.BspProvider.unableToCreateConfigFromMessage(
message
)
)
}
false
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -524,6 +524,12 @@ final class BuildTargets private (
): Option[Iterable[BuildTargetIdentifier]] =
data.fromOptions(_.sourceBuildTargets(sourceItem))

def belongsToBuildTarget(nioDir: Path): Boolean =
sourceItems.filter(_.exists).exists { item =>
val nioItem = item.toNIO
nioDir.startsWith(nioItem) || nioItem.startsWith(nioDir)
}

def inverseSourceItem(source: AbsolutePath): Option[AbsolutePath] =
sourceItems.find(item => source.toNIO.startsWith(item.toNIO))

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package scala.meta.internal.metals

import scala.concurrent.ExecutionContext
import scala.concurrent.Future

import scala.meta.internal.builds.BuildTool
import scala.meta.internal.builds.BuildToolSelector
import scala.meta.internal.builds.BuildTools
import scala.meta.internal.builds.VersionRecommendation
import scala.meta.internal.metals.clients.language.MetalsLanguageClient
import scala.meta.internal.semver.SemVer
import scala.meta.io.AbsolutePath

class BuildToolProvider(
buildTools: BuildTools,
tables: Tables,
folder: AbsolutePath,
warnings: ProjectWarnings,
languageClient: MetalsLanguageClient,
)(implicit ec: ExecutionContext) {
private val buildToolSelector: BuildToolSelector = new BuildToolSelector(
languageClient,
tables,
)

def buildTool: Option[BuildTool] =
for {
name <- tables.buildTool.selectedBuildTool()
buildTool <- buildTools.current().find(_.executableName == name)
if isCompatibleVersion(buildTool)
} yield buildTool

def optProjectRoot: Option[AbsolutePath] =
buildTool.map(_.projectRoot).orElse(buildTools.bloopProject)

private def isCompatibleVersion(buildTool: BuildTool): Boolean = {
buildTool match {
case buildTool: VersionRecommendation =>
SemVer.isCompatibleVersion(
buildTool.minimumVersion,
buildTool.version,
)
case _ => true
}
}

/**
* Checks if the version of the build tool is compatible with the version that
* metals expects and returns the current digest of the build tool.
*/
private def verifyBuildTool(buildTool: BuildTool): BuildTool.Verified = {
buildTool match {
case buildTool: VersionRecommendation
if !isCompatibleVersion(buildTool) =>
BuildTool.IncompatibleVersion(buildTool)
case _ =>
buildTool.digestWithRetry(folder) match {
case Some(digest) =>
BuildTool.Found(buildTool, digest)
case None => BuildTool.NoChecksum(buildTool, folder)
}
}
}

def supportedBuildTool(): Future[Option[BuildTool.Found]] = {
buildTools.loadSupported() match {
case Nil => {
if (!buildTools.isAutoConnectable()) {
warnings.noBuildTool()
}
Future(None)
}
case buildTools =>
for {
buildTool <- buildToolSelector.checkForChosenBuildTool(
buildTools
)
} yield {
buildTool.flatMap { bt =>
verifyBuildTool(bt) match {
case found: BuildTool.Found => Some(found)
case warn @ BuildTool.IncompatibleVersion(buildTool) =>
scribe.warn(warn.message)
languageClient.showMessage(
Messages.IncompatibleBuildToolVersion.params(buildTool)
)
None
case warn: BuildTool.NoChecksum =>
scribe.warn(warn.message)
None
}
}
}
}
}

def onNewBuildToolAdded(
newBuildTool: BuildTool,
currentBuildTool: BuildTool,
): Future[Boolean] =
buildToolSelector.onNewBuildToolAdded(newBuildTool, currentBuildTool)
}
Loading

0 comments on commit 0032e23

Please sign in to comment.