Skip to content

Commit

Permalink
improvement: if no build tool fallback to scala-cli
Browse files Browse the repository at this point in the history
  • Loading branch information
kasiaMarek authored and tgodzik committed Jun 29, 2023
1 parent 749a182 commit 7a9751d
Show file tree
Hide file tree
Showing 5 changed files with 119 additions and 31 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package scala.meta.internal.metals

import java.io.File
import java.io.IOException
import java.net.URI
import java.nio.charset.StandardCharsets
Expand Down Expand Up @@ -294,6 +295,16 @@ object MetalsEnrichments

implicit class XtensionAbsolutePathBuffers(path: AbsolutePath) {

def hasScalaFiles: Boolean = {
def isScalaDir(file: File): Boolean = {
file.listFiles().exists { file =>
if (file.isDirectory()) isScalaDir(file)
else file.getName().endsWith(".scala")
}
}
path.isDirectory && isScalaDir(path.toFile)
}

def scalaSourcerootOption: String = s""""-P:semanticdb:sourceroot:$path""""

def javaSourcerootOption: String =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -882,17 +882,20 @@ class MetalsLspService(
def connectTables(): Connection = tables.connect()

def initialized(): Future[Unit] =
Future
.sequence(
List[Future[Unit]](
Future(buildTools.initialize()),
quickConnectToBuildServer().ignoreValue,
slowConnectToBuildServer(forceImport = false).ignoreValue,
Future(workspaceSymbols.indexClasspath()),
Future(formattingProvider.load()),
)
)
.ignoreValue
for {
_ <- maybeSetupScalaCli()
_ <-
Future
.sequence(
List[Future[Unit]](
Future(buildTools.initialize()),
quickConnectToBuildServer().ignoreValue,
slowConnectToBuildServer(forceImport = false).ignoreValue,
Future(workspaceSymbols.indexClasspath()),
Future(formattingProvider.load()),
)
)
} yield ()

def onShutdown(): Unit = {
tables.fingerprints.save(fingerprints.getAllFingerprints())
Expand Down Expand Up @@ -1941,7 +1944,16 @@ class MetalsLspService(
tables.buildServers.chooseServer(ScalaCliBuildTool.name)
quickConnectToBuildServer()
case Some(digest) if isBloopOrEmpty =>
slowConnectToBloopServer(forceImport, buildTool, digest)
for {
_ <-
if (scalaCli.loaded(folder)) scalaCli.stop()
else Future.successful(())
buildChange <- slowConnectToBloopServer(
forceImport,
buildTool,
digest,
)
} yield buildChange
case Some(digest) =>
indexer.reloadWorkspaceAndIndex(
forceImport,
Expand All @@ -1955,6 +1967,20 @@ class MetalsLspService(
}
} yield buildChange

/**
* If there is no auto-connectable build server and no supported build tool is found
* we assume it's a scala-cli project.
*/
def maybeSetupScalaCli(): Future[Unit] = {
if (
!buildTools.isAutoConnectable
&& buildTools.loadSupported.isEmpty
&& folder.hasScalaFiles
) {
scalaCli.setupIDE(folder)
} else Future.successful(())
}

private def slowConnectToBloopServer(
forceImport: Boolean,
buildTool: BuildTool,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ class ScalaCli(
}
}

private lazy val baseCommand = {
private lazy val localScalaCli: Option[Seq[String]] = {

def endsWithCaseInsensitive(s: String, suffix: String): Boolean =
s.length >= suffix.length &&
Expand Down Expand Up @@ -217,7 +217,7 @@ class ScalaCli(
version.exists(ver => minVersion0.compareTo(ver) <= 0)
}

val cliCommand = userConfig().scalaCliLauncher
userConfig().scalaCliLauncher
.filter(_.trim.nonEmpty)
.map(Seq(_))
.orElse {
Expand All @@ -226,34 +226,65 @@ class ScalaCli(
.filter(requireMinVersion(_, ScalaCli.minVersion))
.map(p => Seq(p.toString))
}
.getOrElse {
scribe.warn(
s"scala-cli >= ${ScalaCli.minVersion} not found in PATH, fetching and starting a JVM-based Scala CLI"
)
val cp = ScalaCli.scalaCliClassPath()
Seq(
ScalaCli.javaCommand,
"-cp",
cp.mkString(File.pathSeparator),
ScalaCli.scalaCliMainClass,
)
}
cliCommand ++ Seq("bsp")
}

private lazy val cliCommand = {
localScalaCli.getOrElse {
scribe.warn(
s"scala-cli >= ${ScalaCli.minVersion} not found in PATH, fetching and starting a JVM-based Scala CLI"
)
jvmBased()
}
}

def jvmBased(): Seq[String] = {
val cp = ScalaCli.scalaCliClassPath()
Seq(
ScalaCli.javaCommand,
"-cp",
cp.mkString(File.pathSeparator),
ScalaCli.scalaCliMainClass,
)
}

def loaded(path: AbsolutePath): Boolean =
ifConnectedOrElse(st =>
st.path == path || path.toNIO.startsWith(st.path.toNIO)
)(false)

def setupIDE(path: AbsolutePath): Future[Unit] = {
localScalaCli
.map { cliCommand =>
val command = cliCommand ++ Seq("setup-ide", path.toString())
scribe.info(s"Running $command")
val proc = SystemProcess.run(
command.toList,
path,
redirectErrorOutput = false,
env = Map(),
processOut = None,
processErr = Some(line => scribe.info("Scala CLI: " + line)),
discardInput = false,
threadNamePrefix = "scala-cli-setup-ide",
)
proc.complete.ignoreValue
}
.getOrElse {
start(path)
}
}

def path: Option[AbsolutePath] =
ifConnectedOrElse(st => Option(st.path))(None)

def start(path: AbsolutePath): Future[Unit] = {
disconnectOldBuildServer().onComplete {
case Failure(e) =>
scribe.warn("Error disconnecting old Scala CLI server", e)
case Success(()) =>
}

val command = baseCommand :+ path.toString()
val command = cliCommand :+ "bsp" :+ path.toString()

val connDir = if (path.isDirectory) path else path.parent

Expand Down Expand Up @@ -283,7 +314,7 @@ class ScalaCli(
scribe.error("Error starting Scala CLI", ex)
Success(())
case Success(_) =>
scribe.info("Scala CLI started")
scribe.info(s"Scala CLI started for $path")
Success(())
}
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ class SyntaxErrorLspSuite extends BaseLspSuite("syntax-error") {
} yield ()
}

test("no-build-tool") {
test("no-build-tool") { // we fallback to scala-cli
for {
_ <- initialize(
"""
Expand All @@ -173,7 +173,10 @@ class SyntaxErrorLspSuite extends BaseLspSuite("syntax-error") {
_ <- server.didOpen("A.scala")
_ = assertNoDiff(
client.workspaceDiagnostics,
"""|A.scala:1:20: error: illegal start of simple expression
"""|A.scala:1:20: error: expression expected but '}' found
|object A { val x = }
| ^
|A.scala:1:20: error: illegal start of simple expression
|object A { val x = }
| ^
|""".stripMargin,
Expand Down
17 changes: 17 additions & 0 deletions tests/slow/src/test/scala/tests/scalacli/ScalaCliSuite.scala
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,23 @@ class ScalaCliSuite extends BaseScalaCliSuite(V.scala3) {
} yield ()
}


test("connecting-scalacli-as-fallback") {
cleanWorkspace()
FileLayout.fromString(simpleFileLayout, workspace)
for {
_ <- server.initialize()
_ <- server.initialized()
_ <- server.server.indexingPromise.future
_ <- server.didOpen("MyTests.scala")
_ <- assertDefinitionAtLocation(
"MyTests.scala",
"new Fo@@o",
"foo.sc",
)
} yield ()
}

test("relative-semanticdb-root") {
for {
_ <- scalaCliInitialize(useBsp = false)(
Expand Down

0 comments on commit 7a9751d

Please sign in to comment.