From 934d0953e70b5d90dd834dca0db8d0d0fe57a5a5 Mon Sep 17 00:00:00 2001 From: Katarzyna Marek Date: Wed, 28 Jun 2023 14:35:37 +0100 Subject: [PATCH] improvement: detect `scala-cli` build tool when `project.scala` exists --- .../meta/internal/builds/BuildTools.scala | 8 +- .../internal/builds/ScalaCliBuildTool.scala | 34 +++- .../internal/metals/scalacli/ScalaCli.scala | 153 +++++++++--------- 3 files changed, 113 insertions(+), 82 deletions(-) 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 60c74966c7a..fddb4eb88fc 100644 --- a/metals/src/main/scala/scala/meta/internal/builds/BuildTools.scala +++ b/metals/src/main/scala/scala/meta/internal/builds/BuildTools.scala @@ -66,7 +66,9 @@ final class BuildTools( } def isMill: Boolean = workspace.resolve("build.sc").isFile def isScalaCli: Boolean = - ScalaCliBuildTool.pathsToScalaCliBsp(workspace).exists(_.isFile) + ScalaCliBuildTool + .pathsToScalaCliBsp(workspace) + .exists(_.isFile) || workspace.resolve("project.scala").isFile def isGradle: Boolean = { val defaultGradlePaths = List( "settings.gradle", @@ -86,7 +88,7 @@ final class BuildTools( GradleBuildTool(userConfig), MavenBuildTool(userConfig), MillBuildTool(userConfig), - ScalaCliBuildTool(workspace), + ScalaCliBuildTool(workspace, userConfig), ) } @@ -113,7 +115,7 @@ final class BuildTools( if (isGradle) buf += GradleBuildTool(userConfig) if (isMaven) buf += MavenBuildTool(userConfig) if (isMill) buf += MillBuildTool(userConfig) - if (isScalaCli) buf += ScalaCliBuildTool(workspace) + if (isScalaCli) buf += ScalaCliBuildTool(workspace, userConfig) buf.result() } diff --git a/metals/src/main/scala/scala/meta/internal/builds/ScalaCliBuildTool.scala b/metals/src/main/scala/scala/meta/internal/builds/ScalaCliBuildTool.scala index 263c6e72216..4578940320b 100644 --- a/metals/src/main/scala/scala/meta/internal/builds/ScalaCliBuildTool.scala +++ b/metals/src/main/scala/scala/meta/internal/builds/ScalaCliBuildTool.scala @@ -6,11 +6,34 @@ import scala.concurrent.Future import scala.meta.internal.metals.BuildInfo import scala.meta.internal.metals.MetalsEnrichments._ +import scala.meta.internal.metals.UserConfiguration import scala.meta.internal.metals.scalacli.ScalaCli import scala.meta.io.AbsolutePath -class ScalaCliBuildTool(val workspaceVersion: Option[String]) - extends BuildTool { +class ScalaCliBuildTool( + val workspaceVersion: Option[String], + userConfig: () => UserConfiguration, +) extends BuildTool + with BuildServerProvider { + + lazy val runScalaCliCommand: Option[Seq[String]] = + ScalaCli.localScalaCli(userConfig()) + + override def createBspFileArgs(workspace: AbsolutePath): List[String] = + runScalaCliCommand.getOrElse(Seq("scala-cli")).toList ++ List( + "setup-ide", + workspace.toString(), + ) + + override def workspaceSupportsBsp(workspace: AbsolutePath): Boolean = { + val supports = runScalaCliCommand.nonEmpty + if (!supports) { + scribe.info( + s"No scala-cli runner with version >= ${minimumVersion} found" + ) + } + supports + } override def bloopInstall( workspace: AbsolutePath, @@ -40,7 +63,10 @@ object ScalaCliBuildTool { root.resolve(".bsp").resolve("scala.json"), ) - def apply(root: AbsolutePath): ScalaCliBuildTool = { + def apply( + root: AbsolutePath, + userConfig: () => UserConfiguration, + ): ScalaCliBuildTool = { val workspaceFolderVersions = for { path <- pathsToScalaCliBsp(root) @@ -48,7 +74,7 @@ object ScalaCliBuildTool { json = ujson.read(text) version <- json("version").strOpt } yield version - new ScalaCliBuildTool(workspaceFolderVersions.headOption) + new ScalaCliBuildTool(workspaceFolderVersions.headOption, userConfig) } } diff --git a/metals/src/main/scala/scala/meta/internal/metals/scalacli/ScalaCli.scala b/metals/src/main/scala/scala/meta/internal/metals/scalacli/ScalaCli.scala index f323f2357b9..6308cbdf6ce 100644 --- a/metals/src/main/scala/scala/meta/internal/metals/scalacli/ScalaCli.scala +++ b/metals/src/main/scala/scala/meta/internal/metals/scalacli/ScalaCli.scala @@ -152,81 +152,8 @@ class ScalaCli( } } - private lazy val localScalaCli: Option[Seq[String]] = { - - def endsWithCaseInsensitive(s: String, suffix: String): Boolean = - s.length >= suffix.length && - s.regionMatches( - true, - s.length - suffix.length, - suffix, - 0, - suffix.length, - ) - - def findInPath(app: String): Option[Path] = { - val asIs = Paths.get(app) - if (Paths.get(app).getNameCount >= 2) Some(asIs) - else { - def pathEntries = - Option(System.getenv("PATH")).iterator - .flatMap(_.split(File.pathSeparator).iterator) - def pathExts = - if (Properties.isWin) - Option(System.getenv("PATHEXT")).iterator - .flatMap(_.split(File.pathSeparator).iterator) - else Iterator("") - def matches = for { - dir <- pathEntries - ext <- pathExts - app0 = if (endsWithCaseInsensitive(app, ext)) app else app + ext - path = Paths.get(dir).resolve(app0) - if Files.isExecutable(path) - } yield path - matches.toStream.headOption - } - } - - def readFully(is: InputStream): Array[Byte] = { - val b = new ByteArrayOutputStream - val buf = Array.ofDim[Byte](64) - var read = -1 - while ({ - read = is.read(buf) - read >= 0 - }) - if (read > 0) - b.write(buf, 0, read) - b.toByteArray - } - - def requireMinVersion(executable: Path, minVersion: String): Boolean = { - // "scala-cli version" simply prints its version - val process = - new ProcessBuilder(executable.toAbsolutePath.toString, "version") - .redirectError(ProcessBuilder.Redirect.INHERIT) - .redirectOutput(ProcessBuilder.Redirect.PIPE) - .redirectInput(ProcessBuilder.Redirect.PIPE) - .start() - - val b = readFully(process.getInputStream()) - val version = raw"\d+\.\d+\.\d+".r - .findFirstIn(new String(b, "UTF-8")) - .map(Version(_)) - val minVersion0 = Version(minVersion) - version.exists(ver => minVersion0.compareTo(ver) <= 0) - } - - userConfig().scalaCliLauncher - .filter(_.trim.nonEmpty) - .map(Seq(_)) - .orElse { - findInPath("scala-cli") - .orElse(findInPath("scala")) - .filter(requireMinVersion(_, ScalaCli.minVersion)) - .map(p => Seq(p.toString)) - } - } + private lazy val localScalaCli: Option[Seq[String]] = + ScalaCli.localScalaCli(userConfig()) private lazy val cliCommand = { localScalaCli.getOrElse { @@ -415,4 +342,80 @@ object ScalaCli { importedBuild: ImportedBuild, ) extends ConnectionState } + + def localScalaCli(userConfig: UserConfiguration): Option[Seq[String]] = { + + def endsWithCaseInsensitive(s: String, suffix: String): Boolean = + s.length >= suffix.length && + s.regionMatches( + true, + s.length - suffix.length, + suffix, + 0, + suffix.length, + ) + + def findInPath(app: String): Option[Path] = { + val asIs = Paths.get(app) + if (Paths.get(app).getNameCount >= 2) Some(asIs) + else { + def pathEntries = + Option(System.getenv("PATH")).iterator + .flatMap(_.split(File.pathSeparator).iterator) + def pathExts = + if (Properties.isWin) + Option(System.getenv("PATHEXT")).iterator + .flatMap(_.split(File.pathSeparator).iterator) + else Iterator("") + def matches = for { + dir <- pathEntries + ext <- pathExts + app0 = if (endsWithCaseInsensitive(app, ext)) app else app + ext + path = Paths.get(dir).resolve(app0) + if Files.isExecutable(path) + } yield path + matches.toStream.headOption + } + } + + def readFully(is: InputStream): Array[Byte] = { + val b = new ByteArrayOutputStream + val buf = Array.ofDim[Byte](64) + var read = -1 + while ({ + read = is.read(buf) + read >= 0 + }) + if (read > 0) + b.write(buf, 0, read) + b.toByteArray + } + + def requireMinVersion(executable: Path, minVersion: String): Boolean = { + // "scala-cli version" simply prints its version + val process = + new ProcessBuilder(executable.toAbsolutePath.toString, "version") + .redirectError(ProcessBuilder.Redirect.INHERIT) + .redirectOutput(ProcessBuilder.Redirect.PIPE) + .redirectInput(ProcessBuilder.Redirect.PIPE) + .start() + + val b = readFully(process.getInputStream()) + val version = raw"\d+\.\d+\.\d+".r + .findFirstIn(new String(b, "UTF-8")) + .map(Version(_)) + val minVersion0 = Version(minVersion) + version.exists(ver => minVersion0.compareTo(ver) <= 0) + } + + userConfig.scalaCliLauncher + .filter(_.trim.nonEmpty) + .map(Seq(_)) + .orElse { + findInPath("scala-cli") + .orElse(findInPath("scala")) + .filter(requireMinVersion(_, ScalaCli.minVersion)) + .map(p => Seq(p.toString)) + } + } }