Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for Scala-CLI #919

Closed
wants to merge 38 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
a431f46
draft of scli runner
Maeeen Feb 19, 2023
ed66eb4
Scli actor runs scripts as an SbtActor
Maeeen Feb 20, 2023
4de24bd
Add instrumentation
Maeeen Feb 20, 2023
20c0255
beginning of bsp
Maeeen Feb 22, 2023
84bf730
blabla
Maeeen Feb 22, 2023
e224425
Bsp incomprehension
Maeeen Feb 23, 2023
0346e55
ca
Maeeen Feb 24, 2023
e676e9c
wow bsp runs!
Maeeen Feb 24, 2023
970a914
Refactoring
Maeeen Feb 26, 2023
b12439a
Refactor using Future
Maeeen Feb 27, 2023
4311e11
Handle user directives from code
Maeeen Feb 27, 2023
07eb170
Add correct compilation error handling
Maeeen Mar 3, 2023
f4aa8a7
Remove debug code + add todo
Maeeen Mar 3, 2023
f00cbbb
Handle every issue
Maeeen Mar 4, 2023
49edcaf
Add instrumentation result
Maeeen Mar 5, 2023
88f1c3d
Small refactor
Maeeen Mar 6, 2023
4c77e80
Beginning of tests
Maeeen Mar 14, 2023
7d24174
Refactor instrumentation and add some test
Maeeen Mar 14, 2023
de8903f
Add instrumentation test + some fixes
Maeeen Mar 14, 2023
37b169b
Run now directly by the runner, add cancelation (#1)
Maeeen Mar 21, 2023
f2a0d67
Added log and diagnostics even if it did compile (#2)
Maeeen Mar 27, 2023
b2065f8
better (#3)
Maeeen Apr 13, 2023
9556dce
Fix of `ScastieOffsetParams`
Maeeen Apr 17, 2023
94e7f09
Reload metals option
Maeeen Apr 20, 2023
723b9b2
Fix
Maeeen Apr 24, 2023
ac050e7
UI (#5)
Maeeen May 22, 2023
e59c43c
Tests (#4)
Maeeen May 22, 2023
3e644ff
Added reconnect and timeout parameter
Maeeen Jul 5, 2023
f82d309
Updated build.sbt
Maeeen Jul 6, 2023
aa6460e
Made compilable + rebased on scalacenter/scastie
Maeeen Jul 6, 2023
31f1052
Fixed rebasing, now prorperly rebased to scalacenter/scastie
Maeeen Jul 19, 2023
294f7a8
Fix test of ScliRunner
Maeeen Jul 19, 2023
37b435e
Update the LB to handle SBT and SCli snippets differently (#6)
Maeeen Jul 27, 2023
34a8bfa
Scli lb (#8)
Maeeen Jul 31, 2023
0898f6b
Remove dead letter case of Scli runner
Maeeen Aug 3, 2023
de70093
Update build.sbt to start ScliRunner
Maeeen Aug 3, 2023
c84790a
Merge branch 'main' into main
Maeeen Aug 3, 2023
7484a05
Make ScastieMetalsOption's code argument default to None
Maeeen Aug 11, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 12 additions & 4 deletions api/src/main/scala/com.olegych.scastie.api/ApiModels.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ package com.olegych.scastie.api

import play.api.libs.json._

case object SbtPing
case object SbtPong
case object RunnerPing
case object RunnerPong

case class SbtRunnerConnect(hostname: String, port: Int)
case class RunnerConnect(hostname: String, port: Int)
case object ActorConnected

object SnippetSummary {
Expand Down Expand Up @@ -127,7 +127,9 @@ case class ScalaDependency(
override def toString: String = target.renderSbt(this)
}

case class ScastieMetalsOptions(dependencies: Set[ScalaDependency], scalaTarget: ScalaTarget)
// Note: adding a code parameter is for the metals-runner
// so it can parse dependencies and give support for it :)
case class ScastieMetalsOptions(dependencies: Set[ScalaDependency], scalaTarget: ScalaTarget, code: Option[String] = None)

object ScastieMetalsOptions {
implicit val scastieMetalsOptions: OFormat[ScastieMetalsOptions] = Json.format[ScastieMetalsOptions]
Expand All @@ -141,6 +143,8 @@ sealed trait FailureType {

case class NoResult(msg: String) extends FailureType
case class PresentationCompilerFailure(msg: String) extends FailureType
case class InvalidScalaVersion(msg: String) extends FailureType


object FailureType {
implicit val failureTypeFormat: OFormat[FailureType] = Json.format[FailureType]
Expand All @@ -158,6 +162,10 @@ object ScastieOffsetParams {
implicit val scastieOffsetParams: OFormat[ScastieOffsetParams] = Json.format[ScastieOffsetParams]
}

object InvalidScalaVersion {
implicit val InvalidScalaVersionFormat: OFormat[InvalidScalaVersion] = Json.format[InvalidScalaVersion]
}

case class LSPRequestDTO(options: ScastieMetalsOptions, offsetParams: ScastieOffsetParams)
case class CompletionInfoRequest(options: ScastieMetalsOptions, completionItem: CompletionItemDTO)

Expand Down
58 changes: 58 additions & 0 deletions api/src/main/scala/com.olegych.scastie.api/ScalaTarget.scala
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ object ScalaTarget {
formatNative.writes(native) ++ JsObject(Seq("tpe" -> JsString("Native")))
case dotty: Scala3 =>
formatScala3.writes(dotty) ++ JsObject(Seq("tpe" -> JsString("Scala3")))
case scli: ScalaCli => JsObject(Seq("tpe" -> JsString("ScalaCli")))
}
}

Expand All @@ -91,6 +92,7 @@ object ScalaTarget {
case "Typelevel" => formatTypelevel.reads(json)
case "Native" => formatNative.reads(json)
case "Scala3" | "Dotty" => formatScala3.reads(json)
case "ScalaCli" => JsSuccess(ScalaCli())
case _ => JsError(Seq())
}
case _ => JsError(Seq())
Expand All @@ -109,6 +111,29 @@ object ScalaTarget {
)
)

def fromScalaVersion(version: String): Option[ScalaTarget] = {
if (version.startsWith("3")) {
if (version == "3")
Some(ScalaTarget.Scala3(BuildInfo.latest3))
else
Some(ScalaTarget.Scala3(version))
} else if (version.startsWith("2")) {
if (version == "2")
Some(ScalaTarget.Jvm(BuildInfo.latest213))
else if (version == "2.13")
Some(ScalaTarget.Jvm(BuildInfo.latest213))
else if (version == "2.12")
Some(ScalaTarget.Jvm(BuildInfo.latest212))
else if (version == "2.11")
Some(ScalaTarget.Jvm(BuildInfo.latest211))
else if (version == "2.10")
Some(ScalaTarget.Jvm(BuildInfo.latest210))
else
Some(ScalaTarget.Jvm(version))
} else
None
}

Comment on lines +114 to +136
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From what I see this code is used to parse Scala version for metals. If that is the case, you can't fallback to latest 3 if Scala version starts with 3. Each version of Scala has a fully cross versioned dependency and has to be the proper version e.g. Let's imagine if there is some bugfix in latest3, but the real version is < latest3. The code won't compile resulting in no completions at all even tho it is valid code according to selected version. You should use the full version e.g 3.3.2

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's not actually how it handles the Scala version.

    // case of > using scala 3…
    if (version.startsWith("3")) {
      if (version == "3") // if it is exactly > using scala 3
        Some(ScalaTarget.Scala3(BuildInfo.latest3))
      else // if it is > using scala 3.x.x
        Some(ScalaTarget.Scala3(version))

But now that we actually talk about it, I thought that the format 3.x is illegal. Is it the case?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh I didnt' notice. So is there any reason we are using the latest version for 2.13, 2.12 etc ? I think we shouldn't use latest version for example what will happen if user sets 2.13.10 and wants to show something specific to this version ?

object Jvm {
def default: ScalaTarget = ScalaTarget.Jvm(scalaVersion = BuildInfo.latest213)
}
Expand Down Expand Up @@ -299,4 +324,37 @@ object ScalaTarget {
override def toString: String =
s"Scala $scalaVersion"
}

object ScalaCli {
def default: ScalaTarget = ScalaCli()

def defaultCode: String =
"""|// Hello!
|// Scastie is compatible with Scala CLI! You can use
|// directives: https://scala-cli.virtuslab.org/docs/guides/using-directives/
|
|println("Hi Scala CLI <3")
""".stripMargin
}

case class ScalaCli(scalaBinaryVersion0: String = "") extends ScalaTarget {
override def binaryScalaVersion: String = scalaBinaryVersion0
Maeeen marked this conversation as resolved.
Show resolved Hide resolved

override def scalaVersion: String = ""

override def targetType: ScalaTargetType = ScalaTargetType.ScalaCli

override def scaladexRequest: Map[String,String] =
Map("target" -> "JVM")

override def renderSbt(lib: ScalaDependency): String = "// Non-applicable"

override def sbtConfig: String = "// Non-applicable"

override def sbtPluginsConfig: String = "// Non-applicable"

override def sbtRunCommand(worksheetMode: Boolean): String = ???
Comment on lines +352 to +356
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe it would be better to remove it from parent trait and make it only present in SBT compatible BuildTarget's


override def toString: String = "Scala-CLI"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ object ScalaTargetType {
case "JS" => Some(JS)
case "NATIVE" => Some(Native)
case "TYPELEVEL" => Some(Typelevel)
case "SCALACLI" => Some(ScalaCli)
case _ => None
}
}
Expand All @@ -30,7 +31,8 @@ object ScalaTargetType {
Scala3,
JS,
Native,
Typelevel
Typelevel,
ScalaCli
).map(v => (v.toString, v)).toMap

def reads(json: JsValue): JsResult[ScalaTargetType] = {
Expand Down Expand Up @@ -65,4 +67,8 @@ object ScalaTargetType {
case object Typelevel extends ScalaTargetType {
def defaultScalaTarget: ScalaTarget = ScalaTarget.Typelevel.default
}

case object ScalaCli extends ScalaTargetType {
def defaultScalaTarget: ScalaTarget = ScalaTarget.ScalaCli.default
}
}
40 changes: 40 additions & 0 deletions api/src/main/scala/com.olegych.scastie.api/ScliState.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package com.olegych.scastie.api

import play.api.libs.json._

sealed trait ScliState extends ServerState
object ScliState {
case object Unknown extends ScliState {
override def toString: String = "Unknown"
def isReady: Boolean = true
}

case object Disconnected extends ScliState {
override def toString: String = "Disconnected"
def isReady: Boolean = false
}

implicit object ScliStateFormat extends Format[ScliState] {
def writes(state: ScliState): JsValue = {
JsString(state.toString)
}

private val values =
List(
Unknown,
Disconnected
).map(v => (v.toString, v)).toMap

def reads(json: JsValue): JsResult[ScliState] = {
json match {
case JsString(tpe) => {
values.get(tpe) match {
case Some(v) => JsSuccess(v)
case _ => JsError(Seq())
}
}
case _ => JsError(Seq())
}
}
}
}
6 changes: 5 additions & 1 deletion balancer/src/main/resources/reference.conf
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,13 @@ com.olegych.scastie.balancer {
snippets-dir = ./target/snippets/
old-snippets-dir = ./target/old-snippets/

remote-hostname = "127.0.0.1"
remote-sbt-hostname = "127.0.0.1"
remote-sbt-ports-start = 5150
remote-sbt-ports-size = 1

remote-scli-hostname = "127.0.0.1"
remote-scli-ports-start = 5250
remote-scli-ports-size = 1
}

akka.actor.warn-about-java-serializer-usage = false
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package com.olegych.scastie.balancer

import com.typesafe.config.Config
import akka.actor.ActorSelection
import com.olegych.scastie.api.ActorConnected
import akka.actor.ActorLogging
import akka.actor.Actor
import akka.actor.ActorRef
import scala.concurrent.Future
import akka.pattern.ask
import akka.util.Timeout
import scala.concurrent.duration._
import com.olegych.scastie.api.RunnerPing

abstract class BaseDispatcher[R, S](config: Config) extends Actor with ActorLogging {
case class SocketAddress(host: String, port: Int)

import context._

private def getRemoteActorsPath(
key: String,
runnerName: String,
actorName: String
): Map[SocketAddress, String] = {
val host = config.getString(s"remote-$key-hostname")
val portStart = config.getInt(s"remote-$key-ports-start")
val portSize = config.getInt(s"remote-$key-ports-size")
(0 until portSize).map(_ + portStart)
.map(port => {
val addr = SocketAddress(host, port)
(addr, getRemoteActorPath(runnerName, addr, actorName))
})
.toMap
}

def getRemoteActorPath(
runnerName: String,
runnerAddress: SocketAddress,
actorName: String
) = s"akka://$runnerName@${runnerAddress.host}:${runnerAddress.port}/user/$actorName"

def connectRunner(path: String): ActorSelection = {
val selection = context.actorSelection(path)
selection ! ActorConnected
selection
}

def getRemoteServers(
key: String,
runnerName: String,
actorName: String
): Map[SocketAddress, ActorSelection] = {
getRemoteActorsPath(key, runnerName, actorName).map {
case (address, url) => (address, connectRunner(url))
}
}

def ping(servers: List[ActorSelection]): Future[List[Boolean]] = {
implicit val timeout: Timeout = Timeout(10.seconds)
val futures = servers.map { s =>
(s ? RunnerPing).map { _ =>
log.info(s"pinged $s")
true
}.recover { e =>
log.error(e, s"could not ping $s")
false
}
}
Future.sequence(futures)
}

}
Loading
Loading