Skip to content

Commit

Permalink
Merge pull request #3308 from ckipp01/millBsp
Browse files Browse the repository at this point in the history
Better support for mill BSP
  • Loading branch information
ckipp01 authored Nov 29, 2021
2 parents 1fd0a63 + 2b5bb71 commit eb271f8
Show file tree
Hide file tree
Showing 12 changed files with 226 additions and 52 deletions.
2 changes: 1 addition & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,7 @@ lazy val V = new {
val coursierInterfaces = "1.0.4"
val coursier = "2.0.16"
val ammonite = "2.4.1"
val mill = "0.9.10"
val mill = "0.10.0-M4"
val organizeImportRule = "0.6.0"
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ 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
import scala.meta.internal.builds.ShellRunner
import scala.meta.internal.metals.Messages.BspProvider
Expand All @@ -16,7 +15,7 @@ import scala.meta.io.AbsolutePath
import org.eclipse.lsp4j.MessageActionItem

/**
* Runs a process to create a .bsp entry for a givev buildtool.
* Runs a process to create a .bsp entry for a given buildtool.
*/
final class BspConfigGenerator(
workspace: AbsolutePath,
Expand All @@ -25,12 +24,12 @@ final class BspConfigGenerator(
shellRunner: ShellRunner
)(implicit ec: ExecutionContext) {
def runUnconditionally(
buildTool: BuildTool,
buildTool: BuildServerProvider,
args: List[String]
): Future[BspConfigGenerationStatus] =
shellRunner
.run(
s"${buildTool.executableName} bspConfig",
s"${buildTool.getBuildServerName} bspConfig",
args,
workspace,
buildTool.redirectErrorOutput
Expand All @@ -43,7 +42,7 @@ final class BspConfigGenerator(
*/
def chooseAndGenerate(
buildTools: List[BuildServerProvider]
): Future[(BuildTool, BspConfigGenerationStatus)] = {
): Future[(BuildServerProvider, BspConfigGenerationStatus)] = {
for {
Some(buildTool) <- chooseBuildServerProvider(buildTools)
status <- buildTool.generateBspConfig(
Expand All @@ -61,7 +60,7 @@ final class BspConfigGenerator(
.asScala
.map { choice =>
buildTools.find(buildTool =>
new MessageActionItem(buildTool.executableName) == choice
new MessageActionItem(buildTool.getBuildServerName) == choice
)
}
}
Expand Down
26 changes: 8 additions & 18 deletions metals/src/main/scala/scala/meta/internal/bsp/BspConnector.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ 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
import scala.meta.internal.builds.SbtBuildTool
import scala.meta.internal.metals.BloopServers
Expand Down Expand Up @@ -50,7 +49,7 @@ class BspConnector(

private def resolveExplicit(): Option[BspResolvedResult] = {
tables.buildServers.selectedServer().flatMap { sel =>
if (sel == BspConnector.BLOOP_SELECTED) Some(ResolvedBloop)
if (sel == BloopServers.name) Some(ResolvedBloop)
else
bspServers
.findAvailableServers()
Expand Down Expand Up @@ -210,9 +209,9 @@ class BspConnector(
case buildTool: BuildServerProvider
if !foundServers
.exists(details =>
details.getName() == buildTool.executableName
details.getName() == buildTool.getBuildServerName
) =>
buildTool.executableName -> Left(buildTool)
buildTool.getBuildServerName -> Left(buildTool)
}
.toMap

Expand Down Expand Up @@ -242,18 +241,18 @@ class BspConnector(
* generate a bsp config has happened.
*/
def handleGenerationStatus(
buildTool: BuildTool,
buildTool: BuildServerProvider,
status: BspConfigGenerationStatus
): Boolean = status match {
case BspConfigGenerationStatus.Generated =>
tables.buildServers.chooseServer(buildTool.executableName)
tables.buildServers.chooseServer(buildTool.getBuildServerName)
true
case Cancelled => false
case Failed(exit) =>
exit match {
case Left(exitCode) =>
scribe.error(
s"Creation of .bsp/${buildTool.executableName} failed with exit code: $exitCode"
s"Creation of .bsp/${buildTool.getBuildServerName} failed with exit code: $exitCode"
)
client.showMessage(
Messages.BspProvider.genericUnableToCreateConfig
Expand Down Expand Up @@ -282,14 +281,8 @@ class BspConnector(
args => bspConfigGenerator.runUnconditionally(buildTool, args)
)
.map(status => handleGenerationStatus(buildTool, status))
case Right(connectionDetails)
if connectionDetails.getName == BloopServers.name && currentSelectedServer
.contains(
BspConnector.BLOOP_SELECTED
) =>
Future.successful(false)
case Right(details) if details.getName == BloopServers.name =>
tables.buildServers.chooseServer(BspConnector.BLOOP_SELECTED)
tables.buildServers.chooseServer(details.getName)
if (bloopPresent) {
Future.successful(true)
} else {
Expand All @@ -300,6 +293,7 @@ class BspConnector(
if !currentSelectedServer.contains(details.getName) =>
tables.buildServers.chooseServer(details.getName)
Future.successful(true)
case _ => Future.successful(false)
}
case _ =>
Future.successful(false)
Expand Down Expand Up @@ -334,7 +328,3 @@ class BspConnector(
}
}
}

object BspConnector {
final val BLOOP_SELECTED = "BLOOP"
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,19 @@ trait BuildServerProvider extends BuildTool {
*/
def createBspFileArgs(workspace: AbsolutePath): List[String]

/**
* Whether or not the build tool workspace supports BSP. Many times this is
* limited by the version of the build tool that introduces BSP support.
*/
def workspaceSupportsBsp(workspace: AbsolutePath): Boolean

/**
* Name of the build server if different than the actual build-tool that is
* serving as a build server.
*
* Ex. mill isn't mill, but rather mill-bsp
*/
def buildServerName: Option[String] = None

def getBuildServerName: String = buildServerName.getOrElse(executableName)
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,27 @@ import scala.util.Properties

import scala.meta.internal.metals.BuildInfo
import scala.meta.internal.metals.UserConfiguration
import scala.meta.internal.semver.SemVer
import scala.meta.io.AbsolutePath

case class MillBuildTool(userConfig: () => UserConfiguration)
extends BuildTool
with BloopInstallProvider {
with BloopInstallProvider
with BuildServerProvider {

private def getMillVersion(workspace: AbsolutePath): String = {
import scala.meta.internal.jdk.CollectionConverters._
val millVersionPath = workspace.resolve(".mill-version")
if (millVersionPath.isFile) {
Files
.readAllLines(millVersionPath.toNIO)
.asScala
.headOption
.getOrElse(version)
} else {
version
}
}

private val predefScriptName = "predef.sc"

Expand All @@ -25,31 +41,26 @@ case class MillBuildTool(userConfig: () => UserConfiguration)

override def redirectErrorOutput: Boolean = true

override def bloopInstallArgs(workspace: AbsolutePath): List[String] = {

import scala.meta.internal.jdk.CollectionConverters._
val millVersionPath = workspace.resolve(".mill-version")
val millVersion = if (millVersionPath.isFile) {
Files
.readAllLines(millVersionPath.toNIO)
.asScala
.headOption
.getOrElse(version)
} else {
version
}
private def putTogetherArgs(cmd: List[String], workspace: AbsolutePath) = {
val millVersion = getMillVersion(workspace)

// In some environments (such as WSL or cygwin), mill must be run using interactive mode (-i)
val iOption = if (Properties.isWin) List("-i") else Nil
val cmd =
iOption ::: "--predef" :: predefScriptPath.toString :: "mill.contrib.Bloop/install" :: Nil
val fullcmd = if (Properties.isWin) "-i" :: cmd else cmd

userConfig().millScript match {
case Some(script) =>
script :: cmd
case None =>
embeddedMillWrapper.toString() :: "--mill-version" :: millVersion :: cmd
embeddedMillWrapper
.toString() :: "--mill-version" :: millVersion :: fullcmd
}

}

override def bloopInstallArgs(workspace: AbsolutePath): List[String] = {
val cmd =
"--predef" :: predefScriptPath.toString :: "mill.contrib.Bloop/install" :: Nil
putTogetherArgs(cmd, workspace)
}

override def digest(workspace: AbsolutePath): Option[String] =
Expand All @@ -63,7 +74,7 @@ case class MillBuildTool(userConfig: () => UserConfiguration)

override def toString(): String = "Mill"

def executableName = "mill"
override def executableName = "mill"

private def predefScript =
"import $ivy.`com.lihaoyi::mill-contrib-bloop:$MILL_VERSION`".getBytes()
Expand All @@ -72,6 +83,29 @@ case class MillBuildTool(userConfig: () => UserConfiguration)
Files.write(tempDir.resolve(predefScriptName), predefScript)
}

override def createBspFileArgs(workspace: AbsolutePath): List[String] = {
val cmd = "mill.bsp.BSP/install" :: Nil
putTogetherArgs(cmd, workspace)
}

override def workspaceSupportsBsp(workspace: AbsolutePath): Boolean = {
val minimumVersionForBsp = "0.10.0-M4"
val millVersion = getMillVersion(workspace)

if (SemVer.isCompatibleVersion(minimumVersionForBsp, millVersion)) {
scribe.info(
s"mill version ${millVersion} detected to use as a bsp server."
)
true
} else {
scribe.warn(
s"Unable to start mill bsp server. Make sure you are using >= mill $minimumVersionForBsp."
)
false
}
}

override val buildServerName: Option[String] = Some("mill-bsp")
}

object MillBuildTool {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1926,12 +1926,12 @@ class MetalsLanguageServer(
}

def ensureAndConnect(
buildTool: BuildTool,
buildTool: BuildServerProvider,
status: BspConfigGenerationStatus
): Unit =
status match {
case Generated =>
tables.buildServers.chooseServer(buildTool.executableName)
tables.buildServers.chooseServer(buildTool.getBuildServerName)
quickConnectToBuildServer().ignoreValue
case Cancelled => ()
case Failed(exit) =>
Expand Down Expand Up @@ -1973,7 +1973,7 @@ class MetalsLanguageServer(
.chooseAndGenerate(buildTools)
.map {
case (
buildTool: BuildTool,
buildTool: BuildServerProvider,
status: BspConfigGenerationStatus
) =>
ensureAndConnect(buildTool, status)
Expand Down Expand Up @@ -2022,7 +2022,7 @@ class MetalsLanguageServer(
possibleBuildTool <- supportedBuildTool
chosenBuildServer = tables.buildServers.selectedServer()
isBloopOrEmpty = chosenBuildServer.isEmpty || chosenBuildServer.exists(
_ == BspConnector.BLOOP_SELECTED
_ == BloopServers.name
)
buildChange <- possibleBuildTool match {
case Some(buildTool) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ object ServerCommands {
|
|The build servers that Metals knows how to detect and start:
| - sbt
| - mill-bsp
|
|Note: while Metals does know how to start Bloop, Bloop will be started when you trigger a build
|import or when you use `bsp-switch` to switch to Bloop.
Expand Down
14 changes: 10 additions & 4 deletions mtags/src/main/scala/scala/meta/internal/semver/SemVer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,23 @@ object SemVer {
milestone: Option[Int]
) {
def >(that: Version): Boolean = {
lazy val baseVersionEqual =
this.major == this.major && this.minor == that.minor && this.patch == that.patch

this.major > that.major ||
(this.major == that.major && this.minor > that.minor) ||
(this.major == that.major && this.minor == that.minor && this.patch > that.patch) ||
// 3.0.0-RC1 > 3.0.0-M1
this.releaseCandidate.isDefined && that.milestone.isDefined ||
baseVersionEqual && this.releaseCandidate.isDefined && that.milestone.isDefined ||
// 3.0.0 > 3.0.0-M2 and 3.0.0 > 3.0.0-RC1
(this.milestone.isEmpty && this.releaseCandidate.isEmpty && (that.milestone.isDefined || that.releaseCandidate.isDefined)) ||
baseVersionEqual && (this.milestone.isEmpty && this.releaseCandidate.isEmpty && (that.milestone.isDefined || that.releaseCandidate.isDefined)) ||
// 3.0.0-RC2 > 3.0.0-RC1
comparePreRelease(that, (v: Version) => v.releaseCandidate) ||
baseVersionEqual && comparePreRelease(
that,
(v: Version) => v.releaseCandidate
) ||
// 3.0.0-M2 > 3.0.0-M1
comparePreRelease(that, (v: Version) => v.milestone)
baseVersionEqual && comparePreRelease(that, (v: Version) => v.milestone)

}

Expand Down
Loading

0 comments on commit eb271f8

Please sign in to comment.