diff --git a/metals/src/main/scala/scala/meta/internal/bsp/BspConnector.scala b/metals/src/main/scala/scala/meta/internal/bsp/BspConnector.scala index e7af5112b86..8d728b4020e 100644 --- a/metals/src/main/scala/scala/meta/internal/bsp/BspConnector.scala +++ b/metals/src/main/scala/scala/meta/internal/bsp/BspConnector.scala @@ -5,8 +5,8 @@ import java.nio.file.Files import scala.concurrent.ExecutionContext import scala.concurrent.Future -import scala.meta.internal.builds.BazelBuildTool import scala.meta.internal.bsp.BspConfigGenerationStatus._ +import scala.meta.internal.builds.BazelBuildTool import scala.meta.internal.builds.BuildServerProvider import scala.meta.internal.builds.BuildTools import scala.meta.internal.builds.SbtBuildTool @@ -118,7 +118,8 @@ class BspConnector( bspServers .findAvailableServers() .collectFirst { - case details if details.getName == BazelBuildTool.bspName => + case details + if details.getName == BazelBuildTool.bspName => tables.buildServers.chooseServer(BazelBuildTool.bspName) bspServers.newServer(workspace, details).map(Some(_)) } diff --git a/metals/src/main/scala/scala/meta/internal/builds/BazelBuildTool.scala b/metals/src/main/scala/scala/meta/internal/builds/BazelBuildTool.scala index 5e9d30b98b8..8a5a2f0ee1d 100644 --- a/metals/src/main/scala/scala/meta/internal/builds/BazelBuildTool.scala +++ b/metals/src/main/scala/scala/meta/internal/builds/BazelBuildTool.scala @@ -1,17 +1,19 @@ package scala.meta.internal.builds +import java.util.concurrent.TimeUnit + import scala.concurrent.ExecutionContext import scala.concurrent.Future +import scala.meta.internal.metals.Messages +import scala.meta.internal.metals.Messages.ImportBuild import scala.meta.internal.metals.MetalsEnrichments._ +import scala.meta.internal.metals.Tables import scala.meta.internal.metals.UserConfiguration +import scala.meta.internal.process.ExitCodes import scala.meta.io.AbsolutePath + import coursierapi.Dependency -import scala.meta.internal.metals.Messages.ImportBuild -import scala.meta.internal.metals.Messages -import scala.meta.internal.metals.Tables -import scala.meta.internal.process.ExitCodes -import java.util.concurrent.TimeUnit import org.eclipse.lsp4j.services.LanguageClient case class BazelBuildTool(userConfig: () => UserConfiguration) @@ -36,7 +38,7 @@ case class BazelBuildTool(userConfig: () => UserConfiguration) override def recommendedVersion: String = version - override def version: String = "2.7.1" + override def version: String = BazelBuildTool.version override def toString: String = "Bazel" @@ -51,26 +53,30 @@ case class BazelBuildTool(userConfig: () => UserConfiguration) object BazelBuildTool { val name: String = "bazel" val bspName: String = "bazelbsp" + val version: String = "2.7.1" + + val mainClass = "org.jetbrains.bsp.bazel.install.Install" private val coursierArgs = List( - "cs", "launch", "org.jetbrains.bsp:bazel-bsp:2.7.1", "-M", - "org.jetbrains.bsp.bazel.install.Install", + "cs", + "launch", + s"org.jetbrains.bsp:bazel-bsp:$version", + "-M", + mainClass, ) private val dependency = Dependency.of( "org.jetbrains.bsp", "bazel-bsp", - "2.7.1", + version, ) - private val mainClass = "org.jetbrains.bsp.bazel.install.Install" - def writeBazelConfig( shellRunner: ShellRunner, projectDirectory: AbsolutePath, )(implicit ec: ExecutionContext - ) = { + ): Future[WorkspaceLoadedStatus] = { def run() = shellRunner.runJava(dependency, mainClass, projectDirectory, Nil, false) run() @@ -96,13 +102,13 @@ object BazelBuildTool { forceImport: Boolean = false, )(implicit ec: ExecutionContext - ) = { + ): Future[WorkspaceLoadedStatus] = { val notification = tables.dismissedNotifications.ImportChanges if (forceImport) { writeBazelConfig(shellRunner, projectDirectory) } else if (!notification.isDismissed) { languageClient - .showMessageRequest(ImportBuild.params(name)) + .showMessageRequest(ImportBuild.params("Bazel")) .asScala .flatMap { case item if item == Messages.dontShowAgain => diff --git a/metals/src/main/scala/scala/meta/internal/builds/BuildTools.scala b/metals/src/main/scala/scala/meta/internal/builds/BuildTools.scala index 4ab915195e1..722fc20ded7 100644 --- a/metals/src/main/scala/scala/meta/internal/builds/BuildTools.scala +++ b/metals/src/main/scala/scala/meta/internal/builds/BuildTools.scala @@ -90,6 +90,7 @@ final class BuildTools( MavenBuildTool(userConfig), MillBuildTool(userConfig), ScalaCliBuildTool(workspace), + BazelBuildTool(userConfig), ) } diff --git a/metals/src/main/scala/scala/meta/internal/builds/Digest.scala b/metals/src/main/scala/scala/meta/internal/builds/Digest.scala index 1e465e96f50..c8362baec7c 100644 --- a/metals/src/main/scala/scala/meta/internal/builds/Digest.scala +++ b/metals/src/main/scala/scala/meta/internal/builds/Digest.scala @@ -107,7 +107,7 @@ object Digest { def digestBazel( file: AbsolutePath, digest: MessageDigest, - ) = { + ): Boolean = { try { Files .readAllLines(file.toNIO) diff --git a/metals/src/main/scala/scala/meta/internal/metals/MetalsLspService.scala b/metals/src/main/scala/scala/meta/internal/metals/MetalsLspService.scala index 36f6b2005cd..b3aa7014558 100644 --- a/metals/src/main/scala/scala/meta/internal/metals/MetalsLspService.scala +++ b/metals/src/main/scala/scala/meta/internal/metals/MetalsLspService.scala @@ -1913,15 +1913,15 @@ class MetalsLspService( ) case Some(_) if buildTool.executableName == BazelBuildTool.name && !buildTools.isBazelBsp => - BazelBuildTool.maybeWriteBazelConfig( - shellRunner, - folder, - languageClient, - tables, - forceImport - ).flatMap( - _ => quickConnectToBuildServer() - ) + BazelBuildTool + .maybeWriteBazelConfig( + shellRunner, + folder, + languageClient, + tables, + forceImport, + ) + .flatMap(_ => quickConnectToBuildServer()) case Some(_) if buildTool.executableName == ScalaCliBuildTool.name && chosenBuildServer.isEmpty => tables.buildServers.chooseServer(ScalaCliBuildTool.name) @@ -2120,7 +2120,9 @@ class MetalsLspService( possibleBuildTool <- supportedBuildTool() _ <- importBuild(session) _ <- indexer.profiledIndexWorkspace(runDoctorCheck) - _ = possibleBuildTool.map(workspaceReload.persistChecksumStatus(Digest.Status.Installed, _)) + _ = possibleBuildTool.map( + workspaceReload.persistChecksumStatus(Digest.Status.Installed, _) + ) _ = if (session.main.isBloop) checkRunningBloopVersion(session.version) } yield { BuildChange.Reconnected diff --git a/tests/slow/src/test/scala/tests/bazel/BazelLspSuite.scala b/tests/slow/src/test/scala/tests/bazel/BazelLspSuite.scala new file mode 100644 index 00000000000..c8359197750 --- /dev/null +++ b/tests/slow/src/test/scala/tests/bazel/BazelLspSuite.scala @@ -0,0 +1,113 @@ +package tests.bazel + +import scala.meta.internal.builds.BazelBuildTool + +import scala.meta.internal.metals.Messages._ +import scala.meta.internal.metals.MetalsEnrichments._ +import scala.meta.io.AbsolutePath + +import tests.BaseImportSuite +import scala.meta.internal.builds.BazelDigest +import tests.BazelServerInitializer +import tests.BazelBuildLayout + +class BazelLspSuite + extends BaseImportSuite("bazel-import", BazelServerInitializer) { + val scalaVersion = "2.13.6" + val buildTool: BazelBuildTool = BazelBuildTool(() => userConfig) + + val bazelVersion = "6.2.1" + + val libPath = "src/main/scala/lib" + val cmdPath = "src/main/scala/cmd" + + override def currentDigest( + workspace: AbsolutePath + ): Option[String] = BazelDigest.current(workspace) + + test("basic") { + cleanWorkspace() + for { + _ <- initialize(BazelBuildLayout(workspaceLayout, scalaVersion, bazelVersion)) + _ = assertNoDiff( + client.workspaceMessageRequests, + List( + importBuildMessage, + // create .bazelbsp progress message + BazelBuildTool.mainClass, + allProjectsMisconfigured, + ).mkString("\n"), + ) + _ = assert(workspace.resolve(".bsp/bazelbsp.json").exists) + _ = client.messageRequests.clear() // restart + _ = assertStatus(_.isInstalled) + _ <- server.didChange("WORKSPACE")(_ + "\n# comment") + _ <- server.didSave("WORKSPACE")(identity) + // Comment changes do not trigger "re-import project" request + _ = assertNoDiff(client.workspaceMessageRequests, "") + _ <- server.didChange(s"$cmdPath/BUILD") { text => + text.replace("runner", "runner1") + } + _ = assertNoDiff(client.workspaceMessageRequests, "") + _ = client.importBuildChanges = ImportBuildChanges.yes + _ <- server.didSave(s"$cmdPath/BUILD")(identity) + } yield { + assertNoDiff( + client.workspaceMessageRequests, + List( + importBuildChangesMessage + ).mkString("\n"), + ) + server.assertBuildServerConnection() + } + } + + private val greetingFile = + """|package lib + | + |object Greeting { + | def greet: String = "Hi" + | + | def sayHi = println(s"$greet!") + |} + |""".stripMargin + + private val runnerFile = + """|package cmd + | + |import lib.Greeting + | + |object Runner { + | def main(args: Array[String]): Unit = { + | Greeting.sayHi + | } + |} + |""".stripMargin + + private val workspaceLayout = + s"""|/$libPath/BUILD + |load("@io_bazel_rules_scala//scala:scala.bzl", "scala_library") + | + |scala_library( + | name = "greeting", + | srcs = ["Greeting.scala"], + | visibility = ["//src/main/scala/cmd:__pkg__"], + |) + | + |/$libPath/Greeting.scala + |$greetingFile + | + |/$cmdPath/BUILD + |load("@io_bazel_rules_scala//scala:scala.bzl", "scala_binary") + | + |scala_binary( + | name = "runner", + | main_class = "cmd.Runner", + | srcs = ["Runner.scala"], + | deps = ["//src/main/scala/lib:greeting"], + |) + | + |/$cmdPath/Runner.scala + |$runnerFile + |""".stripMargin +} diff --git a/tests/unit/src/main/scala/tests/BaseImportSuite.scala b/tests/unit/src/main/scala/tests/BaseImportSuite.scala index 886f7f38f9d..72a1a743ce8 100644 --- a/tests/unit/src/main/scala/tests/BaseImportSuite.scala +++ b/tests/unit/src/main/scala/tests/BaseImportSuite.scala @@ -23,6 +23,9 @@ abstract class BaseImportSuite( def progressMessage: String = bloopInstallProgress(buildTool.executableName).message + def allProjectsMisconfigured: String = + CheckDoctor.allProjectsMisconfigured + def currentDigest(workspace: AbsolutePath): Option[String] def currentChecksum(): String = diff --git a/tests/unit/src/main/scala/tests/BuildServerInitializer.scala b/tests/unit/src/main/scala/tests/BuildServerInitializer.scala index 30ba5962c6b..53a696fe462 100644 --- a/tests/unit/src/main/scala/tests/BuildServerInitializer.scala +++ b/tests/unit/src/main/scala/tests/BuildServerInitializer.scala @@ -173,3 +173,26 @@ object MillServerInitializer extends BuildServerInitializer { } } } + +object BazelServerInitializer extends BuildServerInitializer { + this: BaseLspSuite => + override def initialize( + workspace: AbsolutePath, + server: TestingServer, + client: TestingClient, + expectError: Boolean, + workspaceFolders: List[String] = Nil, + )(implicit ec: ExecutionContext): Future[InitializeResult] = { + for { + initializeResult <- server.initialize() + // Import build using Bazel + _ = client.importBuild = ImportBuild.yes + _ <- server.initialized() + } yield { + if (!expectError) { + server.assertBuildServerConnection() + } + initializeResult + } + } +} diff --git a/tests/unit/src/main/scala/tests/BuildServerLayout.scala b/tests/unit/src/main/scala/tests/BuildServerLayout.scala index 1f4b7aea8bf..6ae8a79b3a7 100644 --- a/tests/unit/src/main/scala/tests/BuildServerLayout.scala +++ b/tests/unit/src/main/scala/tests/BuildServerLayout.scala @@ -63,3 +63,61 @@ object MillBuildLayout extends BuildToolLayout { |${apply(sourceLayout, scalaVersion)} |""".stripMargin } + +object BazelBuildLayout extends BuildToolLayout { + + override def apply(sourceLayout: String, scalaVersion: String): String = + s"""|/WORKSPACE + |${workspaceFileLayout(scalaVersion)} + |$sourceLayout + |""".stripMargin + + def apply( + sourceLayout: String, + scalaVersion: String, + bazelVersion: String, + ): String = + s"""|/.bazelversion + |$bazelVersion + |${apply(sourceLayout, scalaVersion)} + |""".stripMargin + + def workspaceFileLayout(scalaVersion: String): String = + s"""|load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") + | + |skylib_version = "1.0.3" + | + |http_archive( + | name = "bazel_skylib", + | sha256 = "1c531376ac7e5a180e0237938a2536de0c54d93f5c278634818e0efc952dd56c", + | type = "tar.gz", + | url = "https://mirror.bazel.build/github.com/bazelbuild/bazel-skylib/releases/download/{}/bazel-skylib-{}.tar.gz".format(skylib_version, skylib_version), + |) + | + |http_archive( + | name = "io_bazel_rules_scala", + | sha256 = "77a3b9308a8780fff3f10cdbbe36d55164b85a48123033f5e970fdae262e8eb2", + | strip_prefix = "rules_scala-20220201", + | type = "zip", + | url = "https://github.com/bazelbuild/rules_scala/releases/download/20220201/rules_scala-20220201.zip", + |) + | + |load("@io_bazel_rules_scala//:scala_config.bzl", "scala_config") + | + |scala_config(scala_version = "$scalaVersion") + | + |load("@io_bazel_rules_scala//scala:scala.bzl", "scala_repositories") + | + |scala_repositories() + | + |load("@rules_proto//proto:repositories.bzl", "rules_proto_dependencies", "rules_proto_toolchains") + | + |rules_proto_dependencies() + | + |rules_proto_toolchains() + | + |load("@io_bazel_rules_scala//scala:toolchains.bzl", "scala_register_toolchains") + | + |scala_register_toolchains() + |""".stripMargin +}