-
Notifications
You must be signed in to change notification settings - Fork 337
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feature: Support Bazel as a build tool (#3233)
* Support Bazel as a build tool - wip * Auto-install bazel-bsp If `WORKSPACE` is found, and `.bsp/bazelbsp.json` is not found, install bazel-bsp 2.7.1 and write the config file to `.bsp/bazelbsp.json`. * Refresh workspace when build changed we should update metals-vscode so it fires textDocument/didSave * fix: Fix PathTrie with empty paths When aths where empty, FileWatcher was watching the entire file system * feat: Detect changes in Bazel-related files Adds two improvements: 1. After changing BUILD, WORKSPACE or *.bzl file, user will be prompted to import build. 2. If .bazelbsp is not present, user will be asked if they want to create it (the same way as with .bloop) * improvement: Persist checksum on connecting to new build server Previously cheksum was not persisted there, so first save of build file would result in unnecessary reload * improvement: Add basic BazelBsp test * improvement: Strip ANSI coloring from BazelBsp logs * improvement: Add Bazel slow tests to CI * improvement: Add more test for BazelBsp Adds tests for connecting to build server using GenerateBspConfig and ImportBuild commands * improvement: Apply review suggestions * fix: Fix memory leak in supportedBuildTool MetalsLspService.supportedBuildTool was starting new FileWatcher if there was no build tool present, which was never canceled if indexing didn't happen (eg. build tool was never found). It caused memory leak in unit tests, because we were never killing FileWatcher threads. Now we watch .bsp folder by default * improv: Apply review suggestions * bump bazel-bsp to 3.1.0 * improvement: Bump bazelbsp to 3.1.0 Since 3.0.0 build targets are empty by default. This can be unintuitive for users, so if there is no .bazelproject file we default to all build targets. Also fixes issue with running build server twice on Metals initialize * bugfix: Ignore non scala or java build targets * improvement: Look for projectview in ijwb directory * improvement: Add diagnostics check to bazel tests * improvement: Warning message for no semanticdb in bazel projects * improvement: Download bazelbsp dependency --------- Co-authored-by: Kamil Podsiadlo <[email protected]> Co-authored-by: Rikito Taniguchi <[email protected]> Co-authored-by: Jakub Ciesluk <[email protected]> Co-authored-by: Tomasz Godzik <[email protected]>
- Loading branch information
1 parent
30f8ab5
commit 76caf3b
Showing
24 changed files
with
686 additions
and
40 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
184 changes: 184 additions & 0 deletions
184
metals/src/main/scala/scala/meta/internal/builds/BazelBuildTool.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,184 @@ | ||
package scala.meta.internal.builds | ||
|
||
import java.util.concurrent.TimeUnit | ||
|
||
import scala.concurrent.ExecutionContext | ||
import scala.concurrent.Future | ||
|
||
import scala.meta.internal.metals.JavaBinary | ||
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 coursierapi.Fetch | ||
import org.eclipse.lsp4j.services.LanguageClient | ||
|
||
case class BazelBuildTool( | ||
userConfig: () => UserConfiguration, | ||
projectRoot: AbsolutePath, | ||
) extends BuildTool | ||
with BuildServerProvider | ||
with VersionRecommendation { | ||
|
||
override def digest(workspace: AbsolutePath): Option[String] = { | ||
BazelDigest.current(projectRoot) | ||
} | ||
|
||
def createBspFileArgs(workspace: AbsolutePath): Option[List[String]] = | ||
Option.when(workspaceSupportsBsp)(composeArgs()) | ||
|
||
def workspaceSupportsBsp: Boolean = { | ||
projectRoot.list.exists { | ||
case file if file.filename == "WORKSPACE" => true | ||
case _ => false | ||
} | ||
} | ||
|
||
private def composeArgs(): List[String] = { | ||
val classpathSeparator = java.io.File.pathSeparator | ||
val classpath = Fetch | ||
.create() | ||
.withDependencies(BazelBuildTool.dependency) | ||
.fetch() | ||
.asScala | ||
.mkString(classpathSeparator) | ||
List( | ||
JavaBinary(userConfig().javaHome), | ||
"-classpath", | ||
classpath, | ||
BazelBuildTool.mainClass, | ||
) ++ BazelBuildTool.projectViewArgs(projectRoot) | ||
} | ||
|
||
override def minimumVersion: String = "3.0.0" | ||
|
||
override def recommendedVersion: String = version | ||
|
||
override def version: String = BazelBuildTool.version | ||
|
||
override def toString: String = "Bazel" | ||
|
||
override def executableName = BazelBuildTool.name | ||
|
||
override val forcesBuildServer = true | ||
|
||
override def buildServerName: String = BazelBuildTool.bspName | ||
|
||
} | ||
|
||
object BazelBuildTool { | ||
val name: String = "bazel" | ||
val bspName: String = "bazelbsp" | ||
val version: String = "3.1.0" | ||
|
||
val mainClass = "org.jetbrains.bsp.bazel.install.Install" | ||
|
||
val dependency: Dependency = Dependency.of( | ||
"org.jetbrains.bsp", | ||
"bazel-bsp", | ||
version, | ||
) | ||
|
||
private def hasProjectView(dir: AbsolutePath): Option[AbsolutePath] = | ||
dir.list.find(_.filename.endsWith(".bazelproject")) | ||
|
||
private def existingProjectView( | ||
projectRoot: AbsolutePath | ||
): Option[AbsolutePath] = | ||
List(projectRoot, projectRoot.resolve("ijwb"), projectRoot.resolve(".ijwb")) | ||
.filter(_.isDirectory) | ||
.flatMap(hasProjectView) | ||
.headOption | ||
|
||
private def projectViewArgs(projectRoot: AbsolutePath): List[String] = { | ||
existingProjectView(projectRoot) match { | ||
case Some(projectView) => | ||
List("-p", projectView.toRelative(projectRoot).toString()) | ||
case None => | ||
List( | ||
"-t", | ||
"//...", | ||
) | ||
} | ||
} | ||
|
||
def writeBazelConfig( | ||
shellRunner: ShellRunner, | ||
projectDirectory: AbsolutePath, | ||
javaHome: Option[String], | ||
)(implicit | ||
ec: ExecutionContext | ||
): Future[WorkspaceLoadedStatus] = { | ||
def run() = | ||
shellRunner.runJava( | ||
dependency, | ||
mainClass, | ||
projectDirectory, | ||
projectViewArgs(projectDirectory), | ||
javaHome, | ||
false, | ||
) | ||
run() | ||
.flatMap { code => | ||
scribe.info(s"Generate Bazel-BSP process returned code $code") | ||
if (code != 0) run() | ||
else Future.successful(0) | ||
} | ||
.map { | ||
case ExitCodes.Success => WorkspaceLoadedStatus.Installed | ||
case ExitCodes.Cancel => WorkspaceLoadedStatus.Cancelled | ||
case result => | ||
scribe.error("Failed to write Bazel-BSP config to .bsp") | ||
WorkspaceLoadedStatus.Failed(result) | ||
} | ||
} | ||
|
||
def maybeWriteBazelConfig( | ||
shellRunner: ShellRunner, | ||
projectDirectory: AbsolutePath, | ||
languageClient: LanguageClient, | ||
tables: Tables, | ||
javaHome: Option[String], | ||
forceImport: Boolean = false, | ||
)(implicit | ||
ec: ExecutionContext | ||
): Future[WorkspaceLoadedStatus] = { | ||
val notification = tables.dismissedNotifications.ImportChanges | ||
if (forceImport) { | ||
writeBazelConfig(shellRunner, projectDirectory, javaHome) | ||
} else if (!notification.isDismissed) { | ||
languageClient | ||
.showMessageRequest(ImportBuild.params("Bazel")) | ||
.asScala | ||
.flatMap { | ||
case item if item == Messages.dontShowAgain => | ||
notification.dismissForever() | ||
Future.successful(WorkspaceLoadedStatus.Rejected) | ||
case item if item == ImportBuild.yes => | ||
writeBazelConfig(shellRunner, projectDirectory, javaHome) | ||
case _ => | ||
notification.dismiss(2, TimeUnit.MINUTES) | ||
Future.successful(WorkspaceLoadedStatus.Rejected) | ||
|
||
} | ||
} else { | ||
scribe.info( | ||
s"skipping build import with status ${WorkspaceLoadedStatus.Dismissed}" | ||
) | ||
Future.successful(WorkspaceLoadedStatus.Dismissed) | ||
} | ||
} | ||
|
||
def isBazelRelatedPath( | ||
workspace: AbsolutePath, | ||
path: AbsolutePath, | ||
): Boolean = | ||
path.toNIO.startsWith(workspace.toNIO) && | ||
path.isBazelRelatedPath && | ||
!path.isInBazelBspDirectory(workspace) | ||
} |
23 changes: 23 additions & 0 deletions
23
metals/src/main/scala/scala/meta/internal/builds/BazelDigest.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
package scala.meta.internal.builds | ||
|
||
import java.security.MessageDigest | ||
|
||
import scala.meta.internal.metals.MetalsEnrichments._ | ||
import scala.meta.io.AbsolutePath | ||
|
||
object BazelDigest extends Digestable { | ||
override protected def digestWorkspace( | ||
workspace: AbsolutePath, | ||
digest: MessageDigest, | ||
): Boolean = { | ||
workspace.listRecursive.forall { | ||
case file | ||
if file.isBazelRelatedPath && !file.isInBazelBspDirectory( | ||
workspace | ||
) => | ||
Digest.digestFile(file, digest) | ||
case _ => | ||
true | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.