Skip to content

Commit

Permalink
improvement: detect scala-cli build tool when project.scala exists
Browse files Browse the repository at this point in the history
  • Loading branch information
kasiaMarek committed Jun 28, 2023
1 parent 5eeb537 commit 934d095
Show file tree
Hide file tree
Showing 3 changed files with 113 additions and 82 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -86,7 +88,7 @@ final class BuildTools(
GradleBuildTool(userConfig),
MavenBuildTool(userConfig),
MillBuildTool(userConfig),
ScalaCliBuildTool(workspace),
ScalaCliBuildTool(workspace, userConfig),
)
}

Expand All @@ -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()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -40,15 +63,18 @@ 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)
text <- path.readTextOpt
json = ujson.read(text)
version <- json("version").strOpt
} yield version
new ScalaCliBuildTool(workspaceFolderVersions.headOption)
new ScalaCliBuildTool(workspaceFolderVersions.headOption, userConfig)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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))
}
}
}

0 comments on commit 934d095

Please sign in to comment.