mirror of
https://github.com/enso-org/enso.git
synced 2024-11-22 22:10:15 +03:00
New Language Server API Implementations / Mocks (#1875)
This commit is contained in:
parent
4235d345aa
commit
86fcd86055
@ -1,5 +1,12 @@
|
||||
# Enso Next
|
||||
|
||||
## Tooling
|
||||
|
||||
- Implement parts of the new Language Server API related to library support
|
||||
([#1875](https://github.com/enso-org/enso/pull/1875)). Parts of the API are
|
||||
still mocked internally, but they are supported externally for testing
|
||||
purposes.
|
||||
|
||||
# Enso 0.2.14 (2021-07-15)
|
||||
|
||||
## Interpreter/Runtime
|
||||
|
20
build.sbt
20
build.sbt
@ -233,6 +233,7 @@ lazy val enso = (project in file("."))
|
||||
logger.jvm,
|
||||
pkg,
|
||||
cli,
|
||||
`task-progress-notifications`,
|
||||
`logging-utils`,
|
||||
`logging-service`,
|
||||
`akka-native`,
|
||||
@ -716,6 +717,20 @@ lazy val cli = project
|
||||
Test / parallelExecution := false
|
||||
)
|
||||
|
||||
lazy val `task-progress-notifications` = project
|
||||
.in(file("lib/scala/task-progress-notifications"))
|
||||
.configs(Test)
|
||||
.settings(
|
||||
version := "0.1",
|
||||
libraryDependencies ++= Seq(
|
||||
"com.beachape" %% "enumeratum-circe" % enumeratumCirceVersion,
|
||||
"org.scalatest" %% "scalatest" % scalatestVersion % Test
|
||||
),
|
||||
Test / parallelExecution := false
|
||||
)
|
||||
.dependsOn(cli)
|
||||
.dependsOn(`json-rpc-server`)
|
||||
|
||||
lazy val `version-output` = (project in file("lib/scala/version-output"))
|
||||
.settings(
|
||||
version := "0.1"
|
||||
@ -798,6 +813,7 @@ lazy val `project-manager` = (project in file("lib/scala/project-manager"))
|
||||
.dependsOn(`version-output`)
|
||||
.dependsOn(editions)
|
||||
.dependsOn(cli)
|
||||
.dependsOn(`task-progress-notifications`)
|
||||
.dependsOn(`polyglot-api`)
|
||||
.dependsOn(`runtime-version-manager`)
|
||||
.dependsOn(`library-manager`)
|
||||
@ -977,6 +993,7 @@ lazy val `language-server` = (project in file("engine/language-server"))
|
||||
),
|
||||
Test / testOptions += Tests
|
||||
.Argument(TestFrameworks.ScalaCheck, "-minSuccessfulTests", "1000"),
|
||||
Test / envVars ++= distributionEnvironmentOverrides,
|
||||
GenerateFlatbuffers.flatcVersion := flatbuffersVersion,
|
||||
Compile / sourceGenerators += GenerateFlatbuffers.task
|
||||
)
|
||||
@ -991,6 +1008,8 @@ lazy val `language-server` = (project in file("engine/language-server"))
|
||||
)
|
||||
.dependsOn(`json-rpc-server-test` % Test)
|
||||
.dependsOn(`json-rpc-server`)
|
||||
.dependsOn(`task-progress-notifications`)
|
||||
.dependsOn(`library-manager`)
|
||||
.dependsOn(`logging-service`)
|
||||
.dependsOn(`polyglot-api`)
|
||||
.dependsOn(`searcher`)
|
||||
@ -998,6 +1017,7 @@ lazy val `language-server` = (project in file("engine/language-server"))
|
||||
.dependsOn(`version-output`)
|
||||
.dependsOn(pkg)
|
||||
.dependsOn(testkit % Test)
|
||||
.dependsOn(`runtime-version-manager-test` % Test)
|
||||
|
||||
lazy val ast = (project in file("lib/scala/ast"))
|
||||
.settings(
|
||||
|
@ -4014,13 +4014,11 @@ the repositories and include them in the result as well.
|
||||
> available. In the future it should emit warnings using proper notification
|
||||
> channels.
|
||||
|
||||
The `update` field is optional and if it is not provided, it defaults to false.
|
||||
|
||||
#### Parameters
|
||||
|
||||
```typescript
|
||||
{
|
||||
update?: Boolean;
|
||||
update: Boolean;
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -1,27 +1,26 @@
|
||||
package org.enso.languageserver.boot
|
||||
|
||||
import java.io.File
|
||||
import java.net.URI
|
||||
import java.time.Clock
|
||||
|
||||
import akka.actor.ActorSystem
|
||||
import org.enso.distribution.{
|
||||
DistributionManager,
|
||||
EditionManager,
|
||||
Environment,
|
||||
LanguageHome
|
||||
}
|
||||
import org.enso.editions.EditionResolver
|
||||
import org.enso.jsonrpc.JsonRpcServer
|
||||
import org.enso.languageserver.boot.DeploymentType.{Azure, Desktop}
|
||||
import org.enso.languageserver.capability.CapabilityRouter
|
||||
import org.enso.languageserver.data._
|
||||
import org.enso.languageserver.effect.ZioExec
|
||||
import org.enso.languageserver.filemanager.{
|
||||
ContentRoot,
|
||||
ContentRootManager,
|
||||
ContentRootManagerActor,
|
||||
ContentRootManagerWrapper,
|
||||
ContentRootWithFile,
|
||||
FileManager,
|
||||
FileSystem,
|
||||
ReceivesTreeUpdatesHandler
|
||||
}
|
||||
import org.enso.languageserver.filemanager._
|
||||
import org.enso.languageserver.http.server.BinaryWebSocketServer
|
||||
import org.enso.languageserver.io._
|
||||
import org.enso.languageserver.libraries.{
|
||||
EditionReferenceResolver,
|
||||
LocalLibraryManager,
|
||||
ProjectSettingsManager
|
||||
}
|
||||
import org.enso.languageserver.monitoring.{
|
||||
HealthCheckEndpoint,
|
||||
IdlenessEndpoint,
|
||||
@ -49,6 +48,9 @@ import org.graalvm.polyglot.Context
|
||||
import org.graalvm.polyglot.io.MessageEndpoint
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
import java.io.File
|
||||
import java.net.URI
|
||||
import java.time.Clock
|
||||
import scala.concurrent.duration._
|
||||
|
||||
/** A main module containing all components of the server.
|
||||
@ -270,20 +272,48 @@ class MainModule(serverConfig: LanguageServerConfig, logLevel: LogLevel) {
|
||||
context
|
||||
)(system.dispatcher)
|
||||
|
||||
val environment = new Environment {}
|
||||
val languageHome = LanguageHome.detectFromExecutableLocation(environment)
|
||||
val distributionManager = new DistributionManager(environment)
|
||||
|
||||
val editionProvider =
|
||||
EditionManager.makeEditionProvider(distributionManager, Some(languageHome))
|
||||
val editionResolver = EditionResolver(editionProvider)
|
||||
val editionReferenceResolver = new EditionReferenceResolver(
|
||||
contentRoot.file,
|
||||
editionProvider,
|
||||
editionResolver
|
||||
)
|
||||
val editionManager = EditionManager(distributionManager, Some(languageHome))
|
||||
|
||||
val projectSettingsManager = system.actorOf(
|
||||
ProjectSettingsManager.props(contentRoot.file, editionResolver),
|
||||
"project-settings-manager"
|
||||
)
|
||||
|
||||
val localLibraryManager = system.actorOf(
|
||||
LocalLibraryManager.props(contentRoot.file, distributionManager),
|
||||
"local-library-manager"
|
||||
)
|
||||
|
||||
val jsonRpcControllerFactory = new JsonConnectionControllerFactory(
|
||||
initializationComponent,
|
||||
bufferRegistry,
|
||||
capabilityRouter,
|
||||
fileManager,
|
||||
contentRootManagerActor,
|
||||
contextRegistry,
|
||||
suggestionsHandler,
|
||||
stdOutController,
|
||||
stdErrController,
|
||||
stdInController,
|
||||
runtimeConnector,
|
||||
idlenessMonitor,
|
||||
languageServerConfig
|
||||
mainComponent = initializationComponent,
|
||||
bufferRegistry = bufferRegistry,
|
||||
capabilityRouter = capabilityRouter,
|
||||
fileManager = fileManager,
|
||||
contentRootManager = contentRootManagerActor,
|
||||
contextRegistry = contextRegistry,
|
||||
suggestionsHandler = suggestionsHandler,
|
||||
stdOutController = stdOutController,
|
||||
stdErrController = stdErrController,
|
||||
stdInController = stdInController,
|
||||
runtimeConnector = runtimeConnector,
|
||||
idlenessMonitor = idlenessMonitor,
|
||||
projectSettingsManager = projectSettingsManager,
|
||||
localLibraryManager = localLibraryManager,
|
||||
editionReferenceResolver = editionReferenceResolver,
|
||||
editionManager = editionManager,
|
||||
config = languageServerConfig
|
||||
)
|
||||
log.trace(
|
||||
"Created JSON connection controller factory [{}].",
|
||||
|
@ -0,0 +1,54 @@
|
||||
package org.enso.languageserver.libraries
|
||||
|
||||
import io.circe.{Decoder, DecodingFailure, Encoder, Json}
|
||||
import io.circe.syntax._
|
||||
import io.circe.generic.auto._
|
||||
|
||||
/** A reference to an edition - either a named edition or an unnamed one
|
||||
* associated with the current project.
|
||||
*/
|
||||
sealed trait EditionReference
|
||||
object EditionReference {
|
||||
|
||||
/** An edition identified by its name. */
|
||||
case class NamedEdition(editionName: String) extends EditionReference
|
||||
|
||||
/** The edition associated with the current project. */
|
||||
case object CurrentProjectEdition extends EditionReference
|
||||
|
||||
object CodecField {
|
||||
val Type = "type"
|
||||
val EditionName = "editionName"
|
||||
}
|
||||
|
||||
object CodecType {
|
||||
val NamedEdition = "NamedEdition"
|
||||
val CurrentProjectEdition = "CurrentProjectEdition"
|
||||
}
|
||||
|
||||
implicit val encoder: Encoder[EditionReference] = {
|
||||
case NamedEdition(editionName) =>
|
||||
Json.obj(
|
||||
CodecField.Type -> CodecType.NamedEdition.asJson,
|
||||
CodecField.EditionName -> editionName.asJson
|
||||
)
|
||||
case CurrentProjectEdition =>
|
||||
Json.obj(CodecField.Type -> CodecType.CurrentProjectEdition.asJson)
|
||||
}
|
||||
|
||||
implicit val decoder: Decoder[EditionReference] = { json =>
|
||||
val typeCursor = json.downField(CodecField.Type)
|
||||
typeCursor.as[String].flatMap {
|
||||
case CodecType.NamedEdition =>
|
||||
Decoder[NamedEdition].tryDecode(json)
|
||||
case CodecType.CurrentProjectEdition => Right(CurrentProjectEdition)
|
||||
case unknownType =>
|
||||
Left(
|
||||
DecodingFailure(
|
||||
s"Unknown EditionReference type [$unknownType].",
|
||||
typeCursor.history
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
package org.enso.languageserver.libraries
|
||||
|
||||
import org.enso.editions.provider.EditionProvider
|
||||
import org.enso.editions.{DefaultEdition, EditionResolver, Editions}
|
||||
import org.enso.languageserver.libraries.EditionReference.NamedEdition
|
||||
import org.enso.pkg.PackageManager
|
||||
|
||||
import java.io.File
|
||||
import scala.util.Try
|
||||
|
||||
/** Resolves [[EditionReference]] to a raw or resolved edition. */
|
||||
class EditionReferenceResolver(
|
||||
projectRoot: File,
|
||||
editionProvider: EditionProvider,
|
||||
editionResolver: EditionResolver
|
||||
) {
|
||||
private lazy val projectPackage =
|
||||
PackageManager.Default.loadPackage(projectRoot).get
|
||||
|
||||
/** Loads the raw edition corresponding to the given [[EditionReference]]. */
|
||||
def resolveReference(
|
||||
editionReference: EditionReference
|
||||
): Try[Editions.RawEdition] = editionReference match {
|
||||
case EditionReference.NamedEdition(editionName) =>
|
||||
editionProvider.findEditionForName(editionName)
|
||||
case EditionReference.CurrentProjectEdition =>
|
||||
Try {
|
||||
projectPackage.config.edition.getOrElse {
|
||||
// TODO [RW] default edition from config (#1864)
|
||||
DefaultEdition.getDefaultEdition
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Resolves all edition dependencies of an edition identified by
|
||||
* [[EditionReference]].
|
||||
*/
|
||||
def resolveEdition(
|
||||
editionReference: EditionReference
|
||||
): Try[Editions.ResolvedEdition] = for {
|
||||
raw <- resolveReference(editionReference)
|
||||
resolved <- editionResolver.resolve(raw).toTry
|
||||
} yield resolved
|
||||
|
||||
/** Resolves all edition dependencies of an edition identified by its name. */
|
||||
def resolveEdition(name: String): Try[Editions.ResolvedEdition] =
|
||||
resolveEdition(NamedEdition(name))
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
package org.enso.languageserver.libraries
|
||||
|
||||
import org.enso.cli.task.{
|
||||
ProgressReporter,
|
||||
ProgressUnit,
|
||||
TaskProgress,
|
||||
TaskProgressImplementation
|
||||
}
|
||||
|
||||
import scala.util.Success
|
||||
|
||||
/** A temporary helper for mocked parts of the API.
|
||||
*
|
||||
* It should be removed soon, when the missing parts are implemented.
|
||||
*/
|
||||
object FakeDownload {
|
||||
|
||||
/** Creates a [[TaskProgress]] which reports progress updates for a few seconds.
|
||||
*
|
||||
* Intended for mocking download-like endpoints.
|
||||
*/
|
||||
def make(seconds: Int = 10): TaskProgress[Unit] = {
|
||||
val tracker = new TaskProgressImplementation[Unit](ProgressUnit.Bytes)
|
||||
val thread = new Thread(() => {
|
||||
val n = (seconds * 10).toLong
|
||||
for (i <- 0L to n) {
|
||||
tracker.reportProgress(i, Some(n))
|
||||
Thread.sleep(100)
|
||||
}
|
||||
tracker.setComplete(Success(()))
|
||||
})
|
||||
thread.start()
|
||||
tracker
|
||||
}
|
||||
|
||||
/** Simulates a download operation reporting progress updates to the
|
||||
* [[ProgressReporter]].
|
||||
*/
|
||||
def simulateDownload(
|
||||
message: String,
|
||||
progressReporter: ProgressReporter,
|
||||
seconds: Int = 10
|
||||
): Unit = {
|
||||
val download = make(seconds = seconds)
|
||||
progressReporter.trackProgress(message, download)
|
||||
download.force()
|
||||
}
|
||||
}
|
@ -0,0 +1,235 @@
|
||||
package org.enso.languageserver.libraries
|
||||
|
||||
import io.circe.Json
|
||||
import io.circe.literal.JsonStringContext
|
||||
import org.enso.editions.{LibraryName, LibraryVersion}
|
||||
import org.enso.jsonrpc.{Error, HasParams, HasResult, Method, Unused}
|
||||
|
||||
object LibraryApi {
|
||||
case object EditionsListAvailable extends Method("editions/listAvailable") {
|
||||
self =>
|
||||
|
||||
case class Params(update: Boolean)
|
||||
|
||||
case class Result(editionNames: Seq[String])
|
||||
|
||||
implicit val hasParams = new HasParams[this.type] {
|
||||
type Params = self.Params
|
||||
}
|
||||
implicit val hasResult = new HasResult[this.type] {
|
||||
type Result = self.Result
|
||||
}
|
||||
}
|
||||
|
||||
case object EditionsResolve extends Method("editions/resolve") {
|
||||
self =>
|
||||
|
||||
case class Params(edition: EditionReference)
|
||||
|
||||
case class Result(engineVersion: String)
|
||||
|
||||
implicit val hasParams = new HasParams[this.type] {
|
||||
type Params = self.Params
|
||||
}
|
||||
implicit val hasResult = new HasResult[this.type] {
|
||||
type Result = self.Result
|
||||
}
|
||||
}
|
||||
|
||||
case object EditionsGetProjectSettings
|
||||
extends Method("editions/getProjectSettings") { self =>
|
||||
|
||||
case class Result(
|
||||
parentEdition: Option[String],
|
||||
preferLocalLibraries: Boolean
|
||||
)
|
||||
|
||||
implicit val hasParams = new HasParams[this.type] {
|
||||
type Params = Unused.type
|
||||
}
|
||||
implicit val hasResult = new HasResult[this.type] {
|
||||
type Result = self.Result
|
||||
}
|
||||
}
|
||||
|
||||
case object EditionsSetParentEdition
|
||||
extends Method("editions/setParentEdition") { self =>
|
||||
|
||||
case class Params(newEditionName: String)
|
||||
|
||||
case class Result(needsRestart: Option[Boolean])
|
||||
|
||||
implicit val hasParams = new HasParams[this.type] {
|
||||
type Params = self.Params
|
||||
}
|
||||
implicit val hasResult = new HasResult[this.type] {
|
||||
type Result = self.Result
|
||||
}
|
||||
}
|
||||
|
||||
case object EditionsSetLocalLibrariesPreference
|
||||
extends Method("editions/setProjectLocalLibrariesPreference") { self =>
|
||||
|
||||
case class Params(preferLocalLibraries: Boolean)
|
||||
|
||||
case class Result(needsRestart: Option[Boolean])
|
||||
|
||||
implicit val hasParams = new HasParams[this.type] {
|
||||
type Params = self.Params
|
||||
}
|
||||
implicit val hasResult = new HasResult[this.type] {
|
||||
type Result = self.Result
|
||||
}
|
||||
}
|
||||
|
||||
case object EditionsListDefinedLibraries
|
||||
extends Method("editions/listDefinedLibraries") { self =>
|
||||
|
||||
case class Params(edition: EditionReference)
|
||||
|
||||
case class Result(availableLibraries: Seq[LibraryEntry])
|
||||
|
||||
implicit val hasParams = new HasParams[this.type] {
|
||||
type Params = self.Params
|
||||
}
|
||||
implicit val hasResult = new HasResult[this.type] {
|
||||
type Result = self.Result
|
||||
}
|
||||
}
|
||||
|
||||
case object LibraryListLocal extends Method("library/listLocal") { self =>
|
||||
|
||||
case class Result(localLibraries: Seq[LibraryEntry])
|
||||
|
||||
implicit val hasParams = new HasParams[this.type] {
|
||||
type Params = Unused.type
|
||||
}
|
||||
implicit val hasResult = new HasResult[this.type] {
|
||||
type Result = self.Result
|
||||
}
|
||||
}
|
||||
|
||||
case object LibraryCreate extends Method("library/create") { self =>
|
||||
|
||||
case class Params(
|
||||
namespace: String,
|
||||
name: String,
|
||||
authors: Seq[String],
|
||||
maintainers: Seq[String],
|
||||
license: String
|
||||
)
|
||||
|
||||
implicit val hasParams = new HasParams[this.type] {
|
||||
type Params = self.Params
|
||||
}
|
||||
implicit val hasResult = new HasResult[this.type] {
|
||||
type Result = Unused.type
|
||||
}
|
||||
}
|
||||
|
||||
case object LibraryGetMetadata extends Method("library/getMetadata") { self =>
|
||||
|
||||
case class Params(namespace: String, name: String)
|
||||
|
||||
case class Result(description: Option[String], tagLine: Option[String])
|
||||
|
||||
implicit val hasParams = new HasParams[this.type] {
|
||||
type Params = self.Params
|
||||
}
|
||||
implicit val hasResult = new HasResult[this.type] {
|
||||
type Result = self.Result
|
||||
}
|
||||
}
|
||||
|
||||
case object LibrarySetMetadata extends Method("library/setMetadata") { self =>
|
||||
|
||||
case class Params(
|
||||
namespace: String,
|
||||
name: String,
|
||||
description: Option[String],
|
||||
tagLine: Option[String]
|
||||
)
|
||||
|
||||
implicit val hasParams = new HasParams[this.type] {
|
||||
type Params = self.Params
|
||||
}
|
||||
implicit val hasResult = new HasResult[this.type] {
|
||||
type Result = Unused.type
|
||||
}
|
||||
}
|
||||
|
||||
case object LibraryPublish extends Method("library/publish") { self =>
|
||||
|
||||
case class Params(
|
||||
namespace: String,
|
||||
name: String,
|
||||
authToken: String,
|
||||
bumpVersionAfterPublish: Option[Boolean]
|
||||
)
|
||||
|
||||
implicit val hasParams = new HasParams[this.type] {
|
||||
type Params = self.Params
|
||||
}
|
||||
implicit val hasResult = new HasResult[this.type] {
|
||||
type Result = Unused.type
|
||||
}
|
||||
}
|
||||
|
||||
case object LibraryPreinstall extends Method("library/preinstall") { self =>
|
||||
|
||||
case class Params(namespace: String, name: String)
|
||||
|
||||
implicit val hasParams = new HasParams[this.type] {
|
||||
type Params = self.Params
|
||||
}
|
||||
implicit val hasResult = new HasResult[this.type] {
|
||||
type Result = Unused.type
|
||||
}
|
||||
}
|
||||
|
||||
case class EditionNotFoundError(editionName: String)
|
||||
extends Error(8001, s"Edition [$editionName] could not be found.") {
|
||||
override def payload: Option[Json] = Some(
|
||||
json""" { "editionName" : $editionName } """
|
||||
)
|
||||
}
|
||||
|
||||
case class LibraryAlreadyExists(libraryName: LibraryName)
|
||||
extends Error(8002, s"Library [$libraryName] already exists.")
|
||||
|
||||
case class LibraryRepositoryAuthenticationError(reason: String)
|
||||
extends Error(8003, s"Authentication failed: $reason.")
|
||||
|
||||
case class LibraryPublishError(reason: String)
|
||||
extends Error(8004, s"Could not publish the library: $reason.")
|
||||
|
||||
case class LibraryUploadError(reason: String)
|
||||
extends Error(8005, s"Could not upload the library: $reason.")
|
||||
|
||||
case class LibraryDownloadError(
|
||||
name: LibraryName,
|
||||
version: LibraryVersion,
|
||||
reason: String
|
||||
) extends Error(8006, s"Could not download the library: $reason.") {
|
||||
override def payload: Option[Json] = Some(
|
||||
json""" {
|
||||
"namespace" : ${name.namespace},
|
||||
"name" : ${name.name},
|
||||
"version" : ${version.toString}
|
||||
} """
|
||||
)
|
||||
}
|
||||
|
||||
case class LocalLibraryNotFound(libraryName: LibraryName)
|
||||
extends Error(8007, s"Local library [$libraryName] has not been found.")
|
||||
|
||||
case class LibraryNotResolved(name: LibraryName)
|
||||
extends Error(8008, s"Could not resolve [$name].") {
|
||||
override def payload: Option[Json] = Some(
|
||||
json""" {
|
||||
"namespace" : ${name.namespace},
|
||||
"name" : ${name.name}
|
||||
} """
|
||||
)
|
||||
}
|
||||
}
|
@ -0,0 +1,91 @@
|
||||
package org.enso.languageserver.libraries
|
||||
|
||||
import io.circe.generic.semiauto._
|
||||
import io.circe.syntax._
|
||||
import io.circe.{Decoder, DecodingFailure, Encoder, Json}
|
||||
import org.enso.editions
|
||||
|
||||
/** An entry in library lists sent to the client.
|
||||
*
|
||||
* @param namespace namespace of the library
|
||||
* @param name name of the library
|
||||
* @param version version of the library
|
||||
*/
|
||||
case class LibraryEntry(
|
||||
namespace: String,
|
||||
name: String,
|
||||
version: LibraryEntry.LibraryVersion
|
||||
)
|
||||
|
||||
object LibraryEntry {
|
||||
|
||||
/** Version of a library. */
|
||||
sealed trait LibraryVersion
|
||||
|
||||
/** A library version that references a locally editable version of the
|
||||
* library.
|
||||
*/
|
||||
case object LocalLibraryVersion extends LibraryVersion
|
||||
|
||||
/** A library version that references a version of the library published in
|
||||
* some repository.
|
||||
*/
|
||||
case class PublishedLibraryVersion(version: String, repositoryUrl: String)
|
||||
extends LibraryVersion
|
||||
|
||||
/** Converts an instance of [[editions.LibraryVersion]] into one that is used
|
||||
* in the Language Server protocol.
|
||||
*/
|
||||
implicit def convertLibraryVersion(
|
||||
libraryVersion: editions.LibraryVersion
|
||||
): LibraryVersion = libraryVersion match {
|
||||
case editions.LibraryVersion.Local => LocalLibraryVersion
|
||||
case editions.LibraryVersion.Published(version, repository) =>
|
||||
PublishedLibraryVersion(version.toString, repository.url)
|
||||
}
|
||||
|
||||
implicit val encoder: Encoder[LibraryEntry] = deriveEncoder[LibraryEntry]
|
||||
implicit val decoder: Decoder[LibraryEntry] = deriveDecoder[LibraryEntry]
|
||||
|
||||
object CodecField {
|
||||
val Type = "type"
|
||||
val Version = "version"
|
||||
val RepositoryUrl = "repositoryUrl"
|
||||
}
|
||||
|
||||
object CodecType {
|
||||
val LocalLibraryVersion = "LocalLibraryVersion"
|
||||
val PublishedLibraryVersion = "PublishedLibraryVersion"
|
||||
}
|
||||
|
||||
implicit val versionEncoder: Encoder[LibraryVersion] = {
|
||||
case LocalLibraryVersion =>
|
||||
Json.obj(CodecField.Type -> CodecType.LocalLibraryVersion.asJson)
|
||||
case PublishedLibraryVersion(version, repositoryUrl) =>
|
||||
Json.obj(
|
||||
CodecField.Type -> CodecType.PublishedLibraryVersion.asJson,
|
||||
CodecField.Version -> version.asJson,
|
||||
CodecField.RepositoryUrl -> repositoryUrl.asJson
|
||||
)
|
||||
}
|
||||
|
||||
implicit val versionDecoder: Decoder[LibraryVersion] = { json =>
|
||||
val typeCursor = json.downField(CodecField.Type)
|
||||
typeCursor.as[String].flatMap {
|
||||
case CodecType.LocalLibraryVersion =>
|
||||
Right(LocalLibraryVersion)
|
||||
case CodecType.PublishedLibraryVersion =>
|
||||
for {
|
||||
version <- json.get[String](CodecField.Version)
|
||||
repositoryUrl <- json.get[String](CodecField.RepositoryUrl)
|
||||
} yield PublishedLibraryVersion(version, repositoryUrl)
|
||||
case unknownType =>
|
||||
Left(
|
||||
DecodingFailure(
|
||||
s"Unknown LibraryVersion type [$unknownType].",
|
||||
typeCursor.history
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,123 @@
|
||||
package org.enso.languageserver.libraries
|
||||
|
||||
import akka.actor.{Actor, Props}
|
||||
import com.typesafe.scalalogging.LazyLogging
|
||||
import org.enso.distribution.{DistributionManager, FileSystem}
|
||||
import org.enso.editions.{Editions, LibraryName}
|
||||
import org.enso.languageserver.libraries.LocalLibraryManagerProtocol._
|
||||
import org.enso.librarymanager.local.LocalLibraryProvider
|
||||
import org.enso.pkg.PackageManager
|
||||
|
||||
import java.io.File
|
||||
import java.nio.file.Files
|
||||
import scala.util.{Failure, Success, Try}
|
||||
|
||||
/** An Actor that manages local libraries. */
|
||||
class LocalLibraryManager(
|
||||
currentProjectRoot: File,
|
||||
distributionManager: DistributionManager
|
||||
) extends Actor
|
||||
with LazyLogging {
|
||||
override def receive: Receive = { case request: Request =>
|
||||
request match {
|
||||
case GetMetadata(_) =>
|
||||
logger.warn(
|
||||
"Getting local library metadata is currently not implemented."
|
||||
)
|
||||
sender() ! Success(GetMetadataResponse(None, None))
|
||||
case SetMetadata(_, _, _) =>
|
||||
logger.error(
|
||||
"Setting local library metadata is currently not implemented."
|
||||
)
|
||||
sender() ! Failure(new NotImplementedError())
|
||||
case ListLocalLibraries =>
|
||||
sender() ! listLocalLibraries()
|
||||
case Create(libraryName, authors, maintainers, license) =>
|
||||
sender() ! createLibrary(libraryName, authors, maintainers, license)
|
||||
case Publish(_, _, _) =>
|
||||
logger.error("Publishing libraries is currently not implemented.")
|
||||
sender() ! Failure(new NotImplementedError())
|
||||
}
|
||||
}
|
||||
|
||||
/** Creates a new local library project.
|
||||
*
|
||||
* The project is created in the first directory of the local library search
|
||||
* path that is writable.
|
||||
*/
|
||||
private def createLibrary(
|
||||
libraryName: LibraryName,
|
||||
authors: Seq[String],
|
||||
maintainers: Seq[String],
|
||||
license: String
|
||||
): Try[Unit] = Try {
|
||||
// TODO [RW] modify protocol to be able to create Contact instances
|
||||
val _ = (authors, maintainers)
|
||||
|
||||
// TODO [RW] make the exceptions more relevant
|
||||
val possibleRoots = LazyList
|
||||
.from(distributionManager.paths.localLibrariesSearchPaths)
|
||||
.filter { path =>
|
||||
Try { if (Files.notExists(path)) Files.createDirectories(path) }
|
||||
Files.isWritable(path)
|
||||
}
|
||||
val librariesRoot = possibleRoots.headOption.getOrElse {
|
||||
throw new RuntimeException(
|
||||
"Cannot find a writable directory on local library path."
|
||||
)
|
||||
}
|
||||
|
||||
val libraryPath =
|
||||
LocalLibraryProvider.resolveLibraryPath(librariesRoot, libraryName)
|
||||
if (Files.exists(libraryPath)) {
|
||||
throw new RuntimeException("Local library already exists")
|
||||
}
|
||||
|
||||
PackageManager.Default.create(
|
||||
libraryPath.toFile,
|
||||
name = libraryName.name,
|
||||
namespace = libraryName.namespace,
|
||||
edition = findCurrentProjectEdition(),
|
||||
license = license
|
||||
)
|
||||
}
|
||||
|
||||
/** Lists all local libraries. */
|
||||
private def listLocalLibraries(): Try[ListLocalLibrariesResponse] = for {
|
||||
libraryNames <- findLocalLibraries()
|
||||
libraryEntries = libraryNames.distinct.map { name =>
|
||||
LibraryEntry(name.namespace, name.name, LibraryEntry.LocalLibraryVersion)
|
||||
}
|
||||
} yield ListLocalLibrariesResponse(libraryEntries)
|
||||
|
||||
private def findLocalLibraries(): Try[Seq[LibraryName]] = Try {
|
||||
for {
|
||||
searchPathRoot <- distributionManager.paths.localLibrariesSearchPaths
|
||||
namespaceDir <- FileSystem
|
||||
.listDirectory(searchPathRoot)
|
||||
.filter(Files.isDirectory(_))
|
||||
nameDir <- FileSystem
|
||||
.listDirectory(namespaceDir)
|
||||
.filter(Files.isDirectory(_))
|
||||
namespace = namespaceDir.getFileName.toString
|
||||
name = nameDir.getFileName.toString
|
||||
} yield LibraryName(namespace, name)
|
||||
}
|
||||
|
||||
/** Finds the edition associated with the current project, if specified in its
|
||||
* config.
|
||||
*/
|
||||
private def findCurrentProjectEdition(): Option[Editions.RawEdition] = {
|
||||
val pkg = PackageManager.Default.loadPackage(currentProjectRoot).get
|
||||
pkg.config.edition
|
||||
}
|
||||
}
|
||||
|
||||
object LocalLibraryManager {
|
||||
def props(
|
||||
currentProjectRoot: File,
|
||||
distributionManager: DistributionManager
|
||||
): Props = Props(
|
||||
new LocalLibraryManager(currentProjectRoot, distributionManager)
|
||||
)
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
package org.enso.languageserver.libraries
|
||||
|
||||
import org.enso.editions.LibraryName
|
||||
|
||||
object LocalLibraryManagerProtocol {
|
||||
|
||||
/** A top class representing any request to the [[LocalLibraryManager]]. */
|
||||
sealed trait Request
|
||||
|
||||
/** A request to get metadata of a library. */
|
||||
case class GetMetadata(libraryName: LibraryName) extends Request
|
||||
|
||||
/** Response to [[GetMetadata]]. */
|
||||
case class GetMetadataResponse(
|
||||
description: Option[String],
|
||||
tagLine: Option[String]
|
||||
)
|
||||
|
||||
/** A request to update metadata of a library. */
|
||||
case class SetMetadata(
|
||||
libraryName: LibraryName,
|
||||
description: Option[String],
|
||||
tagLine: Option[String]
|
||||
) extends Request
|
||||
|
||||
/** A request to list local libraries. */
|
||||
case object ListLocalLibraries extends Request
|
||||
|
||||
/** A response to [[ListLocalLibraries]]. */
|
||||
case class ListLocalLibrariesResponse(libraries: Seq[LibraryEntry])
|
||||
|
||||
/** A request to create a new library project. */
|
||||
case class Create(
|
||||
libraryName: LibraryName,
|
||||
authors: Seq[String],
|
||||
maintainers: Seq[String],
|
||||
license: String
|
||||
) extends Request
|
||||
|
||||
/** A request to publish a library. */
|
||||
case class Publish(
|
||||
libraryName: LibraryName,
|
||||
authToken: String,
|
||||
bumpVersionAfterPublish: Boolean
|
||||
) extends Request
|
||||
}
|
@ -0,0 +1,80 @@
|
||||
package org.enso.languageserver.libraries
|
||||
|
||||
import akka.actor.{Actor, Props}
|
||||
import org.enso.editions.{DefaultEdition, EditionResolver, Editions}
|
||||
import org.enso.pkg.PackageManager
|
||||
|
||||
import java.io.File
|
||||
import scala.util.Try
|
||||
|
||||
/** An Actor that manages edition-related settings of the current project. */
|
||||
class ProjectSettingsManager(
|
||||
projectRoot: File,
|
||||
editionResolver: EditionResolver
|
||||
) extends Actor {
|
||||
import ProjectSettingsManager._
|
||||
|
||||
override def receive: Receive = { case request: Request =>
|
||||
request match {
|
||||
case GetSettings =>
|
||||
sender() ! loadSettings()
|
||||
case SetParentEdition(editionName) =>
|
||||
sender() ! setParentEdition(editionName)
|
||||
case SetPreferLocalLibraries(preferLocalLibraries) =>
|
||||
sender() ! setPreferLocalLibraries(preferLocalLibraries)
|
||||
}
|
||||
}
|
||||
|
||||
private def loadSettings(): Try[SettingsResponse] = for {
|
||||
pkg <- PackageManager.Default.loadPackage(projectRoot)
|
||||
edition = pkg.config.edition.getOrElse(DefaultEdition.getDefaultEdition)
|
||||
} yield SettingsResponse(edition.parent, pkg.config.preferLocalLibraries)
|
||||
|
||||
private def setParentEdition(editionName: String): Try[Unit] = for {
|
||||
pkg <- PackageManager.Default.loadPackage(projectRoot)
|
||||
newEdition = pkg.config.edition match {
|
||||
case Some(edition) => edition.copy(parent = Some(editionName))
|
||||
case None => Editions.Raw.Edition(parent = Some(editionName))
|
||||
}
|
||||
_ <- editionResolver.resolve(newEdition).toTry
|
||||
updated = pkg.updateConfig { config =>
|
||||
config.copy(edition = Some(newEdition))
|
||||
}
|
||||
_ <- updated.save()
|
||||
} yield ()
|
||||
|
||||
private def setPreferLocalLibraries(
|
||||
preferLocalLibraries: Boolean
|
||||
): Try[Unit] = for {
|
||||
pkg <- PackageManager.Default.loadPackage(projectRoot)
|
||||
updated = pkg.updateConfig { config =>
|
||||
config.copy(preferLocalLibraries = preferLocalLibraries)
|
||||
}
|
||||
_ <- updated.save()
|
||||
} yield ()
|
||||
}
|
||||
|
||||
object ProjectSettingsManager {
|
||||
def props(projectRoot: File, editionResolver: EditionResolver): Props = Props(
|
||||
new ProjectSettingsManager(projectRoot, editionResolver)
|
||||
)
|
||||
|
||||
/** A request to the [[ProjectSettingsManager]]. */
|
||||
sealed trait Request
|
||||
|
||||
/** A request to get the current project settings. */
|
||||
case object GetSettings extends Request
|
||||
|
||||
/** Response to [[GetSettings]]. */
|
||||
case class SettingsResponse(
|
||||
parentEdition: Option[String],
|
||||
preferLocalLibraries: Boolean
|
||||
)
|
||||
|
||||
/** A request to set the parent edition for the project. */
|
||||
case class SetParentEdition(editionName: String) extends Request
|
||||
|
||||
/** A request to set the local libraries preference. */
|
||||
case class SetPreferLocalLibraries(preferLocalLibraries: Boolean)
|
||||
extends Request
|
||||
}
|
@ -0,0 +1,81 @@
|
||||
package org.enso.languageserver.libraries.handler
|
||||
|
||||
import akka.actor.{Actor, ActorRef, Cancellable, Props}
|
||||
import com.typesafe.scalalogging.LazyLogging
|
||||
import org.enso.jsonrpc.{Id, Request, ResponseError, ResponseResult}
|
||||
import org.enso.languageserver.filemanager.FileManagerApi.FileSystemError
|
||||
import org.enso.languageserver.libraries.LibraryApi._
|
||||
import org.enso.languageserver.libraries.ProjectSettingsManager
|
||||
import org.enso.languageserver.requesthandler.RequestTimeout
|
||||
import org.enso.languageserver.util.UnhandledLogging
|
||||
|
||||
import scala.concurrent.duration.FiniteDuration
|
||||
import scala.util.{Failure, Success}
|
||||
|
||||
/** A request handler for the `editions/getProjectSettings` endpoint.
|
||||
*
|
||||
* @param timeout request timeout
|
||||
* @param projectSettingsManager a reference to the [[ProjectSettingsManager]]
|
||||
*/
|
||||
class EditionsGetProjectSettingsHandler(
|
||||
timeout: FiniteDuration,
|
||||
projectSettingsManager: ActorRef
|
||||
) extends Actor
|
||||
with LazyLogging
|
||||
with UnhandledLogging {
|
||||
|
||||
import context.dispatcher
|
||||
|
||||
override def receive: Receive = requestStage
|
||||
|
||||
private def requestStage: Receive = {
|
||||
case Request(EditionsGetProjectSettings, id, _) =>
|
||||
projectSettingsManager ! ProjectSettingsManager.GetSettings
|
||||
val cancellable =
|
||||
context.system.scheduler.scheduleOnce(timeout, self, RequestTimeout)
|
||||
context.become(responseStage(id, sender(), cancellable))
|
||||
}
|
||||
|
||||
private def responseStage(
|
||||
id: Id,
|
||||
replyTo: ActorRef,
|
||||
cancellable: Cancellable
|
||||
): Receive = {
|
||||
case RequestTimeout =>
|
||||
replyTo ! RequestTimeout
|
||||
context.stop(self)
|
||||
|
||||
case Success(settings: ProjectSettingsManager.SettingsResponse) =>
|
||||
replyTo ! ResponseResult(
|
||||
EditionsGetProjectSettings,
|
||||
id,
|
||||
EditionsGetProjectSettings.Result(
|
||||
parentEdition = settings.parentEdition,
|
||||
preferLocalLibraries = settings.preferLocalLibraries
|
||||
)
|
||||
)
|
||||
cancellable.cancel()
|
||||
context.stop(self)
|
||||
|
||||
case Failure(exception) =>
|
||||
replyTo ! ResponseError(Some(id), FileSystemError(exception.getMessage))
|
||||
cancellable.cancel()
|
||||
context.stop(self)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
object EditionsGetProjectSettingsHandler {
|
||||
|
||||
/** Creates a configuration object to create
|
||||
* [[EditionsGetProjectSettingsHandler]].
|
||||
*
|
||||
* @param timeout request timeout
|
||||
* @param projectSettingsManager a reference to the
|
||||
* [[ProjectSettingsManager]]
|
||||
*/
|
||||
def props(timeout: FiniteDuration, projectSettingsManager: ActorRef): Props =
|
||||
Props(
|
||||
new EditionsGetProjectSettingsHandler(timeout, projectSettingsManager)
|
||||
)
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
package org.enso.languageserver.libraries.handler
|
||||
|
||||
import akka.actor.{Actor, Props}
|
||||
import com.typesafe.scalalogging.LazyLogging
|
||||
import org.enso.distribution.EditionManager
|
||||
import org.enso.jsonrpc.{Request, ResponseError, ResponseResult}
|
||||
import org.enso.languageserver.filemanager.FileManagerApi.FileSystemError
|
||||
import org.enso.languageserver.libraries.LibraryApi._
|
||||
import org.enso.languageserver.util.UnhandledLogging
|
||||
|
||||
import scala.util.{Failure, Success, Try}
|
||||
|
||||
/** A request handler for the `editions/listAvailable` endpoint.
|
||||
*
|
||||
* It is a partial implementation - it already allows to list existing
|
||||
* editions, but updating is not yet implemented.
|
||||
*
|
||||
* @param editionManager an edition manager instance
|
||||
*/
|
||||
class EditionsListAvailableHandler(editionManager: EditionManager)
|
||||
extends Actor
|
||||
with LazyLogging
|
||||
with UnhandledLogging {
|
||||
override def receive: Receive = {
|
||||
case Request(EditionsListAvailable, id, _: EditionsListAvailable.Params) =>
|
||||
// TODO [RW] once updating editions is implemented this should be made asynchronous
|
||||
Try(editionManager.findAllAvailableEditions()) match {
|
||||
case Success(editions) =>
|
||||
sender() ! ResponseResult(
|
||||
EditionsListAvailable,
|
||||
id,
|
||||
EditionsListAvailable.Result(editions)
|
||||
)
|
||||
case Failure(exception) =>
|
||||
sender() ! ResponseError(
|
||||
Some(id),
|
||||
FileSystemError(exception.toString)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object EditionsListAvailableHandler {
|
||||
|
||||
/** Creates a configuration object to create [[EditionsListAvailableHandler]].
|
||||
*
|
||||
* @param editionManager an edition manager instance
|
||||
*/
|
||||
def props(editionManager: EditionManager): Props = Props(
|
||||
new EditionsListAvailableHandler(editionManager)
|
||||
)
|
||||
}
|
@ -0,0 +1,69 @@
|
||||
package org.enso.languageserver.libraries.handler
|
||||
|
||||
import akka.actor.{Actor, Props}
|
||||
import com.typesafe.scalalogging.LazyLogging
|
||||
import org.enso.jsonrpc.{Request, ResponseError, ResponseResult}
|
||||
import org.enso.languageserver.filemanager.FileManagerApi.FileSystemError
|
||||
import org.enso.languageserver.libraries.{
|
||||
EditionReferenceResolver,
|
||||
LibraryEntry
|
||||
}
|
||||
import org.enso.languageserver.libraries.LibraryApi._
|
||||
import org.enso.languageserver.util.UnhandledLogging
|
||||
|
||||
import scala.util.{Failure, Success}
|
||||
|
||||
/** A request handler for the `editions/listDefinedLibraries` endpoint.
|
||||
*
|
||||
* @param editionReferenceResolver an [[EditionReferenceResolver]] instance
|
||||
*/
|
||||
class EditionsListDefinedLibrariesHandler(
|
||||
editionReferenceResolver: EditionReferenceResolver
|
||||
) extends Actor
|
||||
with LazyLogging
|
||||
with UnhandledLogging {
|
||||
override def receive: Receive = {
|
||||
case Request(
|
||||
EditionsListDefinedLibraries,
|
||||
id,
|
||||
EditionsListDefinedLibraries.Params(reference)
|
||||
) =>
|
||||
val result = for {
|
||||
edition <- editionReferenceResolver.resolveEdition(reference)
|
||||
} yield edition.getAllDefinedLibraries.toSeq.map { case (name, version) =>
|
||||
LibraryEntry(
|
||||
namespace = name.namespace,
|
||||
name = name.name,
|
||||
version = version
|
||||
)
|
||||
}
|
||||
|
||||
result match {
|
||||
case Success(libraries) =>
|
||||
sender() ! ResponseResult(
|
||||
EditionsListDefinedLibraries,
|
||||
id,
|
||||
EditionsListDefinedLibraries.Result(libraries)
|
||||
)
|
||||
|
||||
case Failure(exception) =>
|
||||
// TODO [RW] more detailed errors
|
||||
sender() ! ResponseError(
|
||||
Some(id),
|
||||
FileSystemError(exception.getMessage)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object EditionsListDefinedLibrariesHandler {
|
||||
|
||||
/** Creates a configuration object to create
|
||||
* [[EditionsListDefinedLibrariesHandler]].
|
||||
*
|
||||
* @param editionReferenceResolver an [[EditionReferenceResolver]] instance
|
||||
*/
|
||||
def props(editionReferenceResolver: EditionReferenceResolver): Props = Props(
|
||||
new EditionsListDefinedLibrariesHandler(editionReferenceResolver)
|
||||
)
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
package org.enso.languageserver.libraries.handler
|
||||
|
||||
import akka.actor.{Actor, Props}
|
||||
import com.typesafe.scalalogging.LazyLogging
|
||||
import org.enso.jsonrpc.{Request, ResponseError, ResponseResult}
|
||||
import org.enso.languageserver.filemanager.FileManagerApi.FileSystemError
|
||||
import org.enso.languageserver.libraries.EditionReferenceResolver
|
||||
import org.enso.languageserver.libraries.LibraryApi._
|
||||
import org.enso.languageserver.util.UnhandledLogging
|
||||
|
||||
import scala.util.{Failure, Success}
|
||||
|
||||
/** A request handler for the `editions/resolve` endpoint.
|
||||
*
|
||||
* @param editionReferenceResolver an [[EditionReferenceResolver]] instance
|
||||
*/
|
||||
class EditionsResolveHandler(editionReferenceResolver: EditionReferenceResolver)
|
||||
extends Actor
|
||||
with LazyLogging
|
||||
with UnhandledLogging {
|
||||
override def receive: Receive = {
|
||||
case Request(EditionsResolve, id, EditionsResolve.Params(reference)) =>
|
||||
val result = for {
|
||||
edition <- editionReferenceResolver.resolveEdition(reference)
|
||||
} yield edition.getEngineVersion
|
||||
|
||||
result match {
|
||||
case Success(engineVersion) =>
|
||||
sender() ! ResponseResult(
|
||||
EditionsResolve,
|
||||
id,
|
||||
EditionsResolve.Result(engineVersion.toString)
|
||||
)
|
||||
|
||||
case Failure(exception) =>
|
||||
// TODO [RW] more detailed errors
|
||||
sender() ! ResponseError(
|
||||
Some(id),
|
||||
FileSystemError(exception.getMessage)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object EditionsResolveHandler {
|
||||
|
||||
/** Creates a configuration object to create [[EditionsResolveHandler]].
|
||||
*
|
||||
* @param editionReferenceResolver an [[EditionReferenceResolver]] instance
|
||||
*/
|
||||
def props(editionReferenceResolver: EditionReferenceResolver): Props = Props(
|
||||
new EditionsResolveHandler(editionReferenceResolver)
|
||||
)
|
||||
}
|
@ -0,0 +1,88 @@
|
||||
package org.enso.languageserver.libraries.handler
|
||||
|
||||
import akka.actor.{Actor, ActorRef, Cancellable, Props}
|
||||
import com.typesafe.scalalogging.LazyLogging
|
||||
import org.enso.jsonrpc.{Id, Request, ResponseError, ResponseResult}
|
||||
import org.enso.languageserver.filemanager.FileManagerApi.FileSystemError
|
||||
import org.enso.languageserver.libraries.LibraryApi._
|
||||
import org.enso.languageserver.libraries.ProjectSettingsManager
|
||||
import org.enso.languageserver.requesthandler.RequestTimeout
|
||||
import org.enso.languageserver.util.UnhandledLogging
|
||||
|
||||
import scala.concurrent.duration.FiniteDuration
|
||||
import scala.util.{Failure, Success}
|
||||
|
||||
/** A request handler for the `editions/setParentEdition` endpoint.
|
||||
*
|
||||
* @param timeout request timeout
|
||||
* @param projectSettingsManager a reference to the [[ProjectSettingsManager]]
|
||||
*/
|
||||
class EditionsSetParentEditionHandler(
|
||||
timeout: FiniteDuration,
|
||||
projectSettingsManager: ActorRef
|
||||
) extends Actor
|
||||
with LazyLogging
|
||||
with UnhandledLogging {
|
||||
|
||||
import context.dispatcher
|
||||
|
||||
override def receive: Receive = requestStage
|
||||
|
||||
private def requestStage: Receive = {
|
||||
case Request(
|
||||
EditionsSetParentEdition,
|
||||
id,
|
||||
EditionsSetParentEdition.Params(newEditionName)
|
||||
) =>
|
||||
projectSettingsManager ! ProjectSettingsManager.SetParentEdition(
|
||||
newEditionName
|
||||
)
|
||||
val cancellable =
|
||||
context.system.scheduler.scheduleOnce(timeout, self, RequestTimeout)
|
||||
context.become(responseStage(id, sender(), cancellable))
|
||||
}
|
||||
|
||||
private def responseStage(
|
||||
id: Id,
|
||||
replyTo: ActorRef,
|
||||
cancellable: Cancellable
|
||||
): Receive = {
|
||||
case RequestTimeout =>
|
||||
replyTo ! RequestTimeout
|
||||
context.stop(self)
|
||||
|
||||
case Success(_) =>
|
||||
replyTo ! ResponseResult(
|
||||
EditionsSetParentEdition,
|
||||
id,
|
||||
EditionsSetParentEdition.Result(needsRestart = Some(true))
|
||||
)
|
||||
cancellable.cancel()
|
||||
context.stop(self)
|
||||
|
||||
case Failure(exception) =>
|
||||
replyTo ! ResponseError(
|
||||
Some(id),
|
||||
FileSystemError(
|
||||
s"Failed to update the settings: ${exception.getMessage}"
|
||||
)
|
||||
)
|
||||
cancellable.cancel()
|
||||
context.stop(self)
|
||||
}
|
||||
}
|
||||
|
||||
object EditionsSetParentEditionHandler {
|
||||
|
||||
/** Creates a configuration object to create
|
||||
* [[EditionsSetParentEditionHandler]].
|
||||
*
|
||||
* @param timeout request timeout
|
||||
* @param projectSettingsManager a reference to the
|
||||
* [[ProjectSettingsManager]]
|
||||
*/
|
||||
def props(timeout: FiniteDuration, projectSettingsManager: ActorRef): Props =
|
||||
Props(
|
||||
new EditionsSetParentEditionHandler(timeout, projectSettingsManager)
|
||||
)
|
||||
}
|
@ -0,0 +1,94 @@
|
||||
package org.enso.languageserver.libraries.handler
|
||||
|
||||
import akka.actor.{Actor, ActorRef, Cancellable, Props}
|
||||
import com.typesafe.scalalogging.LazyLogging
|
||||
import org.enso.jsonrpc.{Id, Request, ResponseError, ResponseResult}
|
||||
import org.enso.languageserver.filemanager.FileManagerApi.FileSystemError
|
||||
import org.enso.languageserver.libraries.LibraryApi._
|
||||
import org.enso.languageserver.libraries.ProjectSettingsManager
|
||||
import org.enso.languageserver.requesthandler.RequestTimeout
|
||||
import org.enso.languageserver.util.UnhandledLogging
|
||||
|
||||
import scala.concurrent.duration.FiniteDuration
|
||||
import scala.util.{Failure, Success}
|
||||
|
||||
/** A request handler for the `editions/setProjectLocalLibrariesPreference`
|
||||
* endpoint.
|
||||
*
|
||||
* @param timeout request timeout
|
||||
* @param projectSettingsManager a reference to the [[ProjectSettingsManager]]
|
||||
*/
|
||||
class EditionsSetProjectLocalLibrariesPreferenceHandler(
|
||||
timeout: FiniteDuration,
|
||||
projectSettingsManager: ActorRef
|
||||
) extends Actor
|
||||
with LazyLogging
|
||||
with UnhandledLogging {
|
||||
|
||||
import context.dispatcher
|
||||
|
||||
override def receive: Receive = requestStage
|
||||
|
||||
private def requestStage: Receive = {
|
||||
case Request(
|
||||
EditionsSetLocalLibrariesPreference,
|
||||
id,
|
||||
EditionsSetLocalLibrariesPreference.Params(preferLocalLibraries)
|
||||
) =>
|
||||
projectSettingsManager ! ProjectSettingsManager.SetPreferLocalLibraries(
|
||||
preferLocalLibraries
|
||||
)
|
||||
val cancellable =
|
||||
context.system.scheduler.scheduleOnce(timeout, self, RequestTimeout)
|
||||
context.become(responseStage(id, sender(), cancellable))
|
||||
}
|
||||
|
||||
private def responseStage(
|
||||
id: Id,
|
||||
replyTo: ActorRef,
|
||||
cancellable: Cancellable
|
||||
): Receive = {
|
||||
case RequestTimeout =>
|
||||
replyTo ! RequestTimeout
|
||||
context.stop(self)
|
||||
|
||||
case Success(_) =>
|
||||
replyTo ! ResponseResult(
|
||||
EditionsSetLocalLibrariesPreference,
|
||||
id,
|
||||
EditionsSetLocalLibrariesPreference.Result(needsRestart = Some(true))
|
||||
)
|
||||
cancellable.cancel()
|
||||
context.stop(self)
|
||||
|
||||
case Failure(exception) =>
|
||||
replyTo ! ResponseError(
|
||||
Some(id),
|
||||
FileSystemError(
|
||||
s"Failed to update the settings: ${exception.getMessage}"
|
||||
)
|
||||
)
|
||||
cancellable.cancel()
|
||||
context.stop(self)
|
||||
}
|
||||
}
|
||||
|
||||
object EditionsSetProjectLocalLibrariesPreferenceHandler {
|
||||
|
||||
/** Creates a configuration object to create
|
||||
* [[EditionsSetProjectLocalLibrariesPreferenceHandler]].
|
||||
*
|
||||
* @param timeout request timeout
|
||||
* @param projectSettingsManager a reference to the
|
||||
* [[ProjectSettingsManager]]
|
||||
*/
|
||||
def props(
|
||||
timeout: FiniteDuration,
|
||||
projectSettingsManager: ActorRef
|
||||
): Props = Props(
|
||||
new EditionsSetProjectLocalLibrariesPreferenceHandler(
|
||||
timeout,
|
||||
projectSettingsManager
|
||||
)
|
||||
)
|
||||
}
|
@ -0,0 +1,81 @@
|
||||
package org.enso.languageserver.libraries.handler
|
||||
|
||||
import akka.actor.{Actor, ActorRef, Cancellable, Props}
|
||||
import com.typesafe.scalalogging.LazyLogging
|
||||
import org.enso.editions.LibraryName
|
||||
import org.enso.jsonrpc._
|
||||
import org.enso.languageserver.filemanager.FileManagerApi.FileSystemError
|
||||
import org.enso.languageserver.libraries.LibraryApi._
|
||||
import org.enso.languageserver.libraries.LocalLibraryManagerProtocol
|
||||
import org.enso.languageserver.requesthandler.RequestTimeout
|
||||
import org.enso.languageserver.util.UnhandledLogging
|
||||
|
||||
import scala.concurrent.duration.FiniteDuration
|
||||
import scala.util.{Failure, Success}
|
||||
|
||||
/** A request handler for the `library/create` endpoint.
|
||||
*
|
||||
* @param timeout request timeout
|
||||
* @param localLibraryManager a reference to the LocalLibraryManager
|
||||
*/
|
||||
class LibraryCreateHandler(
|
||||
timeout: FiniteDuration,
|
||||
localLibraryManager: ActorRef
|
||||
) extends Actor
|
||||
with LazyLogging
|
||||
with UnhandledLogging {
|
||||
import context.dispatcher
|
||||
|
||||
override def receive: Receive = requestStage
|
||||
|
||||
private def requestStage: Receive = {
|
||||
case Request(
|
||||
LibraryCreate,
|
||||
id,
|
||||
LibraryCreate.Params(namespace, name, authors, maintainers, license)
|
||||
) =>
|
||||
localLibraryManager ! LocalLibraryManagerProtocol.Create(
|
||||
LibraryName(namespace, name),
|
||||
authors = authors,
|
||||
maintainers = maintainers,
|
||||
license = license
|
||||
)
|
||||
val cancellable =
|
||||
context.system.scheduler.scheduleOnce(timeout, self, RequestTimeout)
|
||||
context.become(responseStage(id, sender(), cancellable))
|
||||
}
|
||||
|
||||
private def responseStage(
|
||||
id: Id,
|
||||
replyTo: ActorRef,
|
||||
cancellable: Cancellable
|
||||
): Receive = {
|
||||
case RequestTimeout =>
|
||||
replyTo ! RequestTimeout
|
||||
context.stop(self)
|
||||
|
||||
case Success(_) =>
|
||||
replyTo ! ResponseResult(LibraryCreate, id, Unused)
|
||||
cancellable.cancel()
|
||||
context.stop(self)
|
||||
|
||||
case Failure(exception) =>
|
||||
// TODO [RW] handle LibraryAlreadyExists error
|
||||
replyTo ! ResponseError(Some(id), FileSystemError(exception.getMessage))
|
||||
cancellable.cancel()
|
||||
context.stop(self)
|
||||
}
|
||||
}
|
||||
|
||||
object LibraryCreateHandler {
|
||||
|
||||
/** Creates a configuration object to create [[LibraryCreateHandler]].
|
||||
*
|
||||
* @param timeout request timeout
|
||||
* @param localLibraryManager a reference to the LocalLibraryManager
|
||||
*/
|
||||
def props(timeout: FiniteDuration, localLibraryManager: ActorRef): Props =
|
||||
Props(
|
||||
new LibraryCreateHandler(timeout, localLibraryManager)
|
||||
)
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
package org.enso.languageserver.libraries.handler
|
||||
|
||||
import akka.actor.{Actor, Props}
|
||||
import com.typesafe.scalalogging.LazyLogging
|
||||
import org.enso.jsonrpc.{Request, ResponseResult}
|
||||
import org.enso.languageserver.libraries.LibraryApi._
|
||||
import org.enso.languageserver.util.UnhandledLogging
|
||||
|
||||
/** A request handler for the `library/create` endpoint.
|
||||
*
|
||||
* It is currently a stub implementation which will be refined later on.
|
||||
*/
|
||||
class LibraryGetMetadataHandler
|
||||
extends Actor
|
||||
with LazyLogging
|
||||
with UnhandledLogging {
|
||||
override def receive: Receive = {
|
||||
case Request(LibraryGetMetadata, id, _: LibraryGetMetadata.Params) =>
|
||||
// TODO [RW] actual implementation
|
||||
sender() ! ResponseResult(
|
||||
LibraryGetMetadata,
|
||||
id,
|
||||
LibraryGetMetadata.Result(None, None)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
object LibraryGetMetadataHandler {
|
||||
|
||||
/** Creates a configuration object to create [[LibraryGetMetadataHandler]]. */
|
||||
def props(): Props = Props(new LibraryGetMetadataHandler)
|
||||
}
|
@ -0,0 +1,74 @@
|
||||
package org.enso.languageserver.libraries.handler
|
||||
|
||||
import akka.actor.{Actor, ActorRef, Cancellable, Props}
|
||||
import com.typesafe.scalalogging.LazyLogging
|
||||
import org.enso.jsonrpc._
|
||||
import org.enso.languageserver.filemanager.FileManagerApi.FileSystemError
|
||||
import org.enso.languageserver.libraries.LibraryApi._
|
||||
import org.enso.languageserver.libraries.LocalLibraryManagerProtocol
|
||||
import org.enso.languageserver.requesthandler.RequestTimeout
|
||||
import org.enso.languageserver.util.UnhandledLogging
|
||||
|
||||
import scala.concurrent.duration.FiniteDuration
|
||||
import scala.util.{Failure, Success}
|
||||
|
||||
/** A request handler for the `library/listLocal` endpoint.
|
||||
*
|
||||
* @param timeout request timeout
|
||||
* @param localLibraryManager a reference to the LocalLibraryManager
|
||||
*/
|
||||
class LibraryListLocalHandler(
|
||||
timeout: FiniteDuration,
|
||||
localLibraryManager: ActorRef
|
||||
) extends Actor
|
||||
with LazyLogging
|
||||
with UnhandledLogging {
|
||||
import context.dispatcher
|
||||
|
||||
override def receive: Receive = requestStage
|
||||
|
||||
private def requestStage: Receive = { case Request(LibraryListLocal, id, _) =>
|
||||
localLibraryManager ! LocalLibraryManagerProtocol.ListLocalLibraries
|
||||
val cancellable =
|
||||
context.system.scheduler.scheduleOnce(timeout, self, RequestTimeout)
|
||||
context.become(responseStage(id, sender(), cancellable))
|
||||
}
|
||||
|
||||
private def responseStage(
|
||||
id: Id,
|
||||
replyTo: ActorRef,
|
||||
cancellable: Cancellable
|
||||
): Receive = {
|
||||
case RequestTimeout =>
|
||||
replyTo ! RequestTimeout
|
||||
context.stop(self)
|
||||
|
||||
case Success(
|
||||
LocalLibraryManagerProtocol.ListLocalLibrariesResponse(libraries)
|
||||
) =>
|
||||
replyTo ! ResponseResult(
|
||||
LibraryListLocal,
|
||||
id,
|
||||
LibraryListLocal.Result(libraries)
|
||||
)
|
||||
cancellable.cancel()
|
||||
context.stop(self)
|
||||
|
||||
case Failure(exception) =>
|
||||
replyTo ! ResponseError(Some(id), FileSystemError(exception.getMessage))
|
||||
cancellable.cancel()
|
||||
context.stop(self)
|
||||
}
|
||||
}
|
||||
object LibraryListLocalHandler {
|
||||
|
||||
/** Creates a configuration object to create [[LibraryListLocalHandler]].
|
||||
*
|
||||
* @param timeout request timeout
|
||||
* @param localLibraryManager a reference to the LocalLibraryManager
|
||||
*/
|
||||
def props(timeout: FiniteDuration, localLibraryManager: ActorRef): Props =
|
||||
Props(
|
||||
new LibraryListLocalHandler(timeout, localLibraryManager)
|
||||
)
|
||||
}
|
@ -0,0 +1,56 @@
|
||||
package org.enso.languageserver.libraries.handler
|
||||
|
||||
import akka.actor.{Actor, Props}
|
||||
import com.typesafe.scalalogging.LazyLogging
|
||||
import org.enso.cli.task.notifications.ActorProgressNotificationForwarder
|
||||
import org.enso.jsonrpc.{Request, ResponseError}
|
||||
import org.enso.languageserver.filemanager.FileManagerApi.FileSystemError
|
||||
import org.enso.languageserver.libraries.FakeDownload
|
||||
import org.enso.languageserver.libraries.LibraryApi._
|
||||
import org.enso.languageserver.util.UnhandledLogging
|
||||
|
||||
/** A request handler for the `library/preinstall` endpoint.
|
||||
*
|
||||
* It is currently a stub implementation which will be refined later on.
|
||||
*/
|
||||
class LibraryPreinstallHandler
|
||||
extends Actor
|
||||
with LazyLogging
|
||||
with UnhandledLogging {
|
||||
override def receive: Receive = {
|
||||
case Request(LibraryPreinstall, id, LibraryPreinstall.Params(_, name)) =>
|
||||
// TODO [RW] actual implementation
|
||||
val progressReporter =
|
||||
ActorProgressNotificationForwarder.translateAndForward(
|
||||
LibraryPreinstall.name,
|
||||
sender()
|
||||
)
|
||||
|
||||
if (name == "Test") {
|
||||
FakeDownload.simulateDownload(
|
||||
"Download Test",
|
||||
progressReporter,
|
||||
seconds = 1
|
||||
)
|
||||
} else {
|
||||
FakeDownload.simulateDownload(
|
||||
"Downloading something...",
|
||||
progressReporter
|
||||
)
|
||||
FakeDownload.simulateDownload(
|
||||
"Downloading something else...",
|
||||
progressReporter
|
||||
)
|
||||
}
|
||||
sender() ! ResponseError(
|
||||
Some(id),
|
||||
FileSystemError("Feature not implemented")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
object LibraryPreinstallHandler {
|
||||
|
||||
/** Creates a configuration object to create [[LibraryPreinstallHandler]]. */
|
||||
def props(): Props = Props(new LibraryPreinstallHandler)
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
package org.enso.languageserver.libraries.handler
|
||||
|
||||
import akka.actor.{Actor, Props}
|
||||
import com.typesafe.scalalogging.LazyLogging
|
||||
import org.enso.jsonrpc.{Request, ResponseError}
|
||||
import org.enso.languageserver.filemanager.FileManagerApi.FileSystemError
|
||||
import org.enso.languageserver.libraries.LibraryApi._
|
||||
import org.enso.languageserver.util.UnhandledLogging
|
||||
|
||||
/** A request handler for the `library/publish` endpoint.
|
||||
*
|
||||
* It is currently a stub implementation which will be refined later on.
|
||||
*/
|
||||
class LibraryPublishHandler
|
||||
extends Actor
|
||||
with LazyLogging
|
||||
with UnhandledLogging {
|
||||
override def receive: Receive = {
|
||||
case Request(LibraryPublish, id, _: LibraryPublish.Params) =>
|
||||
// TODO [RW] actual implementation
|
||||
sender() ! ResponseError(
|
||||
Some(id),
|
||||
FileSystemError("Feature not implemented")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
object LibraryPublishHandler {
|
||||
|
||||
/** Creates a configuration object to create [[LibraryPublishHandler]]. */
|
||||
def props(): Props = Props(new LibraryPublishHandler)
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
package org.enso.languageserver.libraries.handler
|
||||
|
||||
import akka.actor.{Actor, Props}
|
||||
import com.typesafe.scalalogging.LazyLogging
|
||||
import org.enso.jsonrpc.{Request, ResponseError}
|
||||
import org.enso.languageserver.filemanager.FileManagerApi.FileSystemError
|
||||
import org.enso.languageserver.libraries.LibraryApi._
|
||||
import org.enso.languageserver.util.UnhandledLogging
|
||||
|
||||
/** A request handler for the `library/setMetadata` endpoint.
|
||||
*
|
||||
* It is currently a stub implementation which will be refined later on.
|
||||
*/
|
||||
class LibrarySetMetadataHandler
|
||||
extends Actor
|
||||
with LazyLogging
|
||||
with UnhandledLogging {
|
||||
override def receive: Receive = {
|
||||
case Request(LibrarySetMetadata, id, _: LibrarySetMetadata.Params) =>
|
||||
// TODO [RW] actual implementation
|
||||
sender() ! ResponseError(
|
||||
Some(id),
|
||||
FileSystemError("Feature not implemented")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
object LibrarySetMetadataHandler {
|
||||
|
||||
/** Creates a configuration object to create [[LibrarySetMetadataHandler]]. */
|
||||
def props(): Props = Props(new LibrarySetMetadataHandler)
|
||||
}
|
@ -1,11 +1,12 @@
|
||||
package org.enso.languageserver.protocol.json
|
||||
|
||||
import java.util.UUID
|
||||
|
||||
import akka.actor.{Actor, ActorRef, Cancellable, Props, Stash, Status}
|
||||
import akka.pattern.pipe
|
||||
import akka.util.Timeout
|
||||
import com.typesafe.scalalogging.LazyLogging
|
||||
import org.enso.cli.task.ProgressUnit
|
||||
import org.enso.cli.task.notifications.TaskNotificationApi
|
||||
import org.enso.distribution.EditionManager
|
||||
import org.enso.jsonrpc._
|
||||
import org.enso.languageserver.boot.resource.InitializationComponent
|
||||
import org.enso.languageserver.capability.CapabilityApi.{
|
||||
@ -26,6 +27,9 @@ import org.enso.languageserver.filemanager._
|
||||
import org.enso.languageserver.io.InputOutputApi._
|
||||
import org.enso.languageserver.io.OutputKind.{StandardError, StandardOutput}
|
||||
import org.enso.languageserver.io.{InputOutputApi, InputOutputProtocol}
|
||||
import org.enso.languageserver.libraries.EditionReferenceResolver
|
||||
import org.enso.languageserver.libraries.LibraryApi._
|
||||
import org.enso.languageserver.libraries.handler._
|
||||
import org.enso.languageserver.monitoring.MonitoringApi.{InitialPing, Ping}
|
||||
import org.enso.languageserver.monitoring.MonitoringProtocol
|
||||
import org.enso.languageserver.refactoring.RefactoringApi.RenameProject
|
||||
@ -66,7 +70,10 @@ import org.enso.languageserver.text.TextApi._
|
||||
import org.enso.languageserver.text.TextProtocol
|
||||
import org.enso.languageserver.util.UnhandledLogging
|
||||
import org.enso.languageserver.workspace.WorkspaceApi.ProjectInfo
|
||||
import org.enso.polyglot.runtime.Runtime.Api
|
||||
import org.enso.polyglot.runtime.Runtime.Api.ProgressNotification
|
||||
|
||||
import java.util.UUID
|
||||
import scala.concurrent.duration._
|
||||
|
||||
/** An actor handling communications between a single client and the language
|
||||
@ -81,6 +88,7 @@ import scala.concurrent.duration._
|
||||
* @param contextRegistry a router that dispatches execution context requests
|
||||
* @param suggestionsHandler a reference to the suggestions requests handler
|
||||
* @param idlenessMonitor a reference to the idleness monitor actor
|
||||
* @param projectSettingsManager a reference to the project settings manager
|
||||
* @param requestTimeout a request timeout
|
||||
*/
|
||||
class JsonConnectionController(
|
||||
@ -97,6 +105,10 @@ class JsonConnectionController(
|
||||
val stdInController: ActorRef,
|
||||
val runtimeConnector: ActorRef,
|
||||
val idlenessMonitor: ActorRef,
|
||||
val projectSettingsManager: ActorRef,
|
||||
val localLibraryManager: ActorRef,
|
||||
val editionReferenceResolver: EditionReferenceResolver,
|
||||
val editionManager: EditionManager,
|
||||
val languageServerConfig: Config,
|
||||
requestTimeout: FiniteDuration = 10.seconds
|
||||
) extends Actor
|
||||
@ -230,8 +242,7 @@ class JsonConnectionController(
|
||||
InitProtocolConnection.Result(allRoots.map(_.toContentRoot).toSet)
|
||||
)
|
||||
|
||||
val requestHandlers = createRequestHandlers(rpcSession)
|
||||
context.become(initialised(webActor, rpcSession, requestHandlers))
|
||||
initialize(webActor, rpcSession)
|
||||
} else {
|
||||
context.become(
|
||||
waitingForContentRoots(
|
||||
@ -254,6 +265,17 @@ class JsonConnectionController(
|
||||
stash()
|
||||
}
|
||||
|
||||
private def initialize(
|
||||
webActor: ActorRef,
|
||||
rpcSession: JsonSession
|
||||
): Unit = {
|
||||
val requestHandlers = createRequestHandlers(rpcSession)
|
||||
context.become(initialised(webActor, rpcSession, requestHandlers))
|
||||
|
||||
context.system.eventStream
|
||||
.subscribe(self, classOf[Api.ProgressNotification])
|
||||
}
|
||||
|
||||
private def initialised(
|
||||
webActor: ActorRef,
|
||||
rpcSession: JsonSession,
|
||||
@ -364,6 +386,11 @@ class JsonConnectionController(
|
||||
)
|
||||
}
|
||||
|
||||
case Api.ProgressNotification(payload) =>
|
||||
val translated: Notification[_, _] =
|
||||
translateProgressNotification(payload)
|
||||
webActor ! translated
|
||||
|
||||
case req @ Request(method, _, _) if requestHandlers.contains(method) =>
|
||||
refreshIdleTime(method)
|
||||
val handler = context.actorOf(
|
||||
@ -458,10 +485,57 @@ class JsonConnectionController(
|
||||
RedirectStandardError -> RedirectStdErrHandler
|
||||
.props(stdErrController, rpcSession.clientId),
|
||||
FeedStandardInput -> FeedStandardInputHandler.props(stdInController),
|
||||
ProjectInfo -> ProjectInfoHandler.props(languageServerConfig)
|
||||
ProjectInfo -> ProjectInfoHandler.props(languageServerConfig),
|
||||
EditionsGetProjectSettings -> EditionsGetProjectSettingsHandler
|
||||
.props(requestTimeout, projectSettingsManager),
|
||||
EditionsListAvailable -> EditionsListAvailableHandler.props(
|
||||
editionManager
|
||||
),
|
||||
EditionsListDefinedLibraries -> EditionsListDefinedLibrariesHandler
|
||||
.props(editionReferenceResolver),
|
||||
EditionsResolve -> EditionsResolveHandler
|
||||
.props(editionReferenceResolver),
|
||||
EditionsSetParentEdition -> EditionsSetParentEditionHandler
|
||||
.props(requestTimeout, projectSettingsManager),
|
||||
EditionsSetLocalLibrariesPreference -> EditionsSetProjectLocalLibrariesPreferenceHandler
|
||||
.props(requestTimeout, projectSettingsManager),
|
||||
LibraryCreate -> LibraryCreateHandler
|
||||
.props(requestTimeout, localLibraryManager),
|
||||
LibraryListLocal -> LibraryListLocalHandler
|
||||
.props(requestTimeout, localLibraryManager),
|
||||
LibraryGetMetadata -> LibraryGetMetadataHandler.props(),
|
||||
LibraryPreinstall -> LibraryPreinstallHandler.props(),
|
||||
LibraryPublish -> LibraryPublishHandler.props(),
|
||||
LibrarySetMetadata -> LibrarySetMetadataHandler.props()
|
||||
)
|
||||
}
|
||||
|
||||
private def translateProgressNotification(
|
||||
progressNotification: ProgressNotification.NotificationType
|
||||
): Notification[_, _] = progressNotification match {
|
||||
case ProgressNotification.TaskStarted(
|
||||
taskId,
|
||||
relatedOperation,
|
||||
unitStr,
|
||||
total
|
||||
) =>
|
||||
val unit = ProgressUnit.fromString(unitStr)
|
||||
Notification(
|
||||
TaskNotificationApi.TaskStarted,
|
||||
TaskNotificationApi.TaskStarted
|
||||
.Params(taskId, relatedOperation, unit, total)
|
||||
)
|
||||
case ProgressNotification.TaskProgressUpdate(taskId, message, done) =>
|
||||
Notification(
|
||||
TaskNotificationApi.TaskProgressUpdate,
|
||||
TaskNotificationApi.TaskProgressUpdate.Params(taskId, message, done)
|
||||
)
|
||||
case ProgressNotification.TaskFinished(taskId, message, success) =>
|
||||
Notification(
|
||||
TaskNotificationApi.TaskFinished,
|
||||
TaskNotificationApi.TaskFinished.Params(taskId, message, success)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
object JsonConnectionController {
|
||||
@ -493,26 +567,34 @@ object JsonConnectionController {
|
||||
stdInController: ActorRef,
|
||||
runtimeConnector: ActorRef,
|
||||
idlenessMonitor: ActorRef,
|
||||
projectSettingsManager: ActorRef,
|
||||
localLibraryManager: ActorRef,
|
||||
editionReferenceResolver: EditionReferenceResolver,
|
||||
editionManager: EditionManager,
|
||||
languageServerConfig: Config,
|
||||
requestTimeout: FiniteDuration = 10.seconds
|
||||
): Props =
|
||||
Props(
|
||||
new JsonConnectionController(
|
||||
connectionId,
|
||||
mainComponent,
|
||||
bufferRegistry,
|
||||
capabilityRouter,
|
||||
fileManager,
|
||||
contentRootManager,
|
||||
contextRegistry,
|
||||
suggestionsHandler,
|
||||
stdOutController,
|
||||
stdErrController,
|
||||
stdInController,
|
||||
runtimeConnector,
|
||||
idlenessMonitor,
|
||||
languageServerConfig,
|
||||
requestTimeout
|
||||
connectionId = connectionId,
|
||||
mainComponent = mainComponent,
|
||||
bufferRegistry = bufferRegistry,
|
||||
capabilityRouter = capabilityRouter,
|
||||
fileManager = fileManager,
|
||||
contentRootManager = contentRootManager,
|
||||
contextRegistry = contextRegistry,
|
||||
suggestionsHandler = suggestionsHandler,
|
||||
stdOutController = stdOutController,
|
||||
stdErrController = stdErrController,
|
||||
stdInController = stdInController,
|
||||
runtimeConnector = runtimeConnector,
|
||||
idlenessMonitor = idlenessMonitor,
|
||||
projectSettingsManager = projectSettingsManager,
|
||||
localLibraryManager = localLibraryManager,
|
||||
editionReferenceResolver = editionReferenceResolver,
|
||||
editionManager = editionManager,
|
||||
languageServerConfig = languageServerConfig,
|
||||
requestTimeout = requestTimeout
|
||||
)
|
||||
)
|
||||
|
||||
|
@ -1,9 +1,11 @@
|
||||
package org.enso.languageserver.protocol.json
|
||||
|
||||
import akka.actor.{ActorRef, ActorSystem}
|
||||
import org.enso.distribution.EditionManager
|
||||
import org.enso.jsonrpc.ClientControllerFactory
|
||||
import org.enso.languageserver.boot.resource.InitializationComponent
|
||||
import org.enso.languageserver.data.Config
|
||||
import org.enso.languageserver.libraries.EditionReferenceResolver
|
||||
|
||||
import java.util.UUID
|
||||
|
||||
@ -27,6 +29,10 @@ class JsonConnectionControllerFactory(
|
||||
stdInController: ActorRef,
|
||||
runtimeConnector: ActorRef,
|
||||
idlenessMonitor: ActorRef,
|
||||
projectSettingsManager: ActorRef,
|
||||
localLibraryManager: ActorRef,
|
||||
editionReferenceResolver: EditionReferenceResolver,
|
||||
editionManager: EditionManager,
|
||||
config: Config
|
||||
)(implicit system: ActorSystem)
|
||||
extends ClientControllerFactory {
|
||||
@ -39,20 +45,24 @@ class JsonConnectionControllerFactory(
|
||||
override def createClientController(clientId: UUID): ActorRef =
|
||||
system.actorOf(
|
||||
JsonConnectionController.props(
|
||||
clientId,
|
||||
mainComponent,
|
||||
bufferRegistry,
|
||||
capabilityRouter,
|
||||
fileManager,
|
||||
contentRootManager,
|
||||
contextRegistry,
|
||||
suggestionsHandler,
|
||||
stdOutController,
|
||||
stdErrController,
|
||||
stdInController,
|
||||
runtimeConnector,
|
||||
idlenessMonitor,
|
||||
config
|
||||
connectionId = clientId,
|
||||
mainComponent = mainComponent,
|
||||
bufferRegistry = bufferRegistry,
|
||||
capabilityRouter = capabilityRouter,
|
||||
fileManager = fileManager,
|
||||
contentRootManager = contentRootManager,
|
||||
contextRegistry = contextRegistry,
|
||||
suggestionsHandler = suggestionsHandler,
|
||||
stdOutController = stdOutController,
|
||||
stdErrController = stdErrController,
|
||||
stdInController = stdInController,
|
||||
runtimeConnector = runtimeConnector,
|
||||
idlenessMonitor = idlenessMonitor,
|
||||
projectSettingsManager = projectSettingsManager,
|
||||
localLibraryManager = localLibraryManager,
|
||||
editionReferenceResolver = editionReferenceResolver,
|
||||
editionManager = editionManager,
|
||||
languageServerConfig = config
|
||||
)
|
||||
)
|
||||
}
|
||||
|
@ -1,6 +1,11 @@
|
||||
package org.enso.languageserver.protocol.json
|
||||
|
||||
import io.circe.generic.auto._
|
||||
import org.enso.cli.task.notifications.TaskNotificationApi.{
|
||||
TaskFinished,
|
||||
TaskProgressUpdate,
|
||||
TaskStarted
|
||||
}
|
||||
import org.enso.jsonrpc.Protocol
|
||||
import org.enso.languageserver.capability.CapabilityApi.{
|
||||
AcquireCapability,
|
||||
@ -17,6 +22,7 @@ import org.enso.languageserver.search.SearchApi._
|
||||
import org.enso.languageserver.runtime.VisualisationApi._
|
||||
import org.enso.languageserver.session.SessionApi.InitProtocolConnection
|
||||
import org.enso.languageserver.text.TextApi._
|
||||
import org.enso.languageserver.libraries.LibraryApi._
|
||||
import org.enso.languageserver.workspace.WorkspaceApi.ProjectInfo
|
||||
|
||||
object JsonRpc {
|
||||
@ -65,6 +71,21 @@ object JsonRpc {
|
||||
.registerRequest(Import)
|
||||
.registerRequest(RenameProject)
|
||||
.registerRequest(ProjectInfo)
|
||||
.registerRequest(EditionsListAvailable)
|
||||
.registerRequest(EditionsResolve)
|
||||
.registerRequest(EditionsGetProjectSettings)
|
||||
.registerRequest(EditionsSetParentEdition)
|
||||
.registerRequest(EditionsSetLocalLibrariesPreference)
|
||||
.registerRequest(EditionsListDefinedLibraries)
|
||||
.registerRequest(LibraryListLocal)
|
||||
.registerRequest(LibraryCreate)
|
||||
.registerRequest(LibraryGetMetadata)
|
||||
.registerRequest(LibrarySetMetadata)
|
||||
.registerRequest(LibraryPublish)
|
||||
.registerRequest(LibraryPreinstall)
|
||||
.registerNotification(TaskStarted)
|
||||
.registerNotification(TaskProgressUpdate)
|
||||
.registerNotification(TaskFinished)
|
||||
.registerNotification(ForceReleaseCapability)
|
||||
.registerNotification(GrantCapability)
|
||||
.registerNotification(TextDidChange)
|
||||
|
@ -1,6 +1,5 @@
|
||||
license: APLv2
|
||||
name: Standard
|
||||
enso-version: default
|
||||
version: "0.1.0"
|
||||
author: "Enso Team <contact@enso.org>"
|
||||
maintainer: "Enso Team <contact@enso.org>"
|
||||
|
@ -0,0 +1,21 @@
|
||||
package org.enso.languageserver.libraries
|
||||
|
||||
import io.circe.syntax._
|
||||
import org.enso.languageserver.libraries.EditionReference.{
|
||||
CurrentProjectEdition,
|
||||
NamedEdition
|
||||
}
|
||||
import org.scalatest.matchers.should.Matchers
|
||||
import org.scalatest.wordspec.AnyWordSpec
|
||||
|
||||
class EditionNameSerializationSpec extends AnyWordSpec with Matchers {
|
||||
"EditionName" should {
|
||||
"serialize and deserialize to the same thing" in {
|
||||
val edition1: EditionReference = CurrentProjectEdition
|
||||
edition1.asJson.as[EditionReference] shouldEqual Right(edition1)
|
||||
|
||||
val edition2: EditionReference = NamedEdition("Foo-Bar")
|
||||
edition2.asJson.as[EditionReference] shouldEqual Right(edition2)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
package org.enso.languageserver.libraries
|
||||
|
||||
import io.circe.syntax._
|
||||
import org.scalatest.matchers.should.Matchers
|
||||
import org.scalatest.wordspec.AnyWordSpec
|
||||
|
||||
class LibraryEntrySerializationSpec extends AnyWordSpec with Matchers {
|
||||
"LibraryEntry" should {
|
||||
"serialize and deserialize to the same thing" in {
|
||||
val entry1 = LibraryEntry("Foo", "Bar", LibraryEntry.LocalLibraryVersion)
|
||||
entry1.asJson.as[LibraryEntry] shouldEqual Right(entry1)
|
||||
|
||||
val entry2 = LibraryEntry(
|
||||
"Foo",
|
||||
"Bar",
|
||||
LibraryEntry.PublishedLibraryVersion("1.2.3", "https://example.com/")
|
||||
)
|
||||
entry2.asJson.as[LibraryEntry] shouldEqual Right(entry2)
|
||||
}
|
||||
}
|
||||
}
|
@ -1,15 +1,15 @@
|
||||
package org.enso.languageserver.websocket.json
|
||||
|
||||
import java.nio.file.Files
|
||||
import java.util.UUID
|
||||
|
||||
import akka.testkit.TestProbe
|
||||
import io.circe.literal._
|
||||
import io.circe.parser.parse
|
||||
import io.circe.syntax.EncoderOps
|
||||
import org.apache.commons.io.FileUtils
|
||||
import org.enso.distribution.{DistributionManager, EditionManager, LanguageHome}
|
||||
import org.enso.editions.EditionResolver
|
||||
import org.enso.jsonrpc.test.JsonRpcServerTestKit
|
||||
import org.enso.jsonrpc.{ClientControllerFactory, Protocol}
|
||||
import org.enso.languageserver.TestClock
|
||||
import org.enso.languageserver.boot.resource.{
|
||||
DirectoriesInitialization,
|
||||
RepoInitialization,
|
||||
@ -21,6 +21,11 @@ import org.enso.languageserver.effect.ZioExec
|
||||
import org.enso.languageserver.event.InitializedEvent
|
||||
import org.enso.languageserver.filemanager._
|
||||
import org.enso.languageserver.io._
|
||||
import org.enso.languageserver.libraries.{
|
||||
EditionReferenceResolver,
|
||||
LocalLibraryManager,
|
||||
ProjectSettingsManager
|
||||
}
|
||||
import org.enso.languageserver.monitoring.IdlenessMonitor
|
||||
import org.enso.languageserver.protocol.json.{
|
||||
JsonConnectionControllerFactory,
|
||||
@ -30,22 +35,28 @@ import org.enso.languageserver.refactoring.ProjectNameChangedEvent
|
||||
import org.enso.languageserver.runtime.{ContextRegistry, RuntimeFailureMapper}
|
||||
import org.enso.languageserver.search.SuggestionsHandler
|
||||
import org.enso.languageserver.session.SessionRouter
|
||||
import org.enso.languageserver.TestClock
|
||||
import org.enso.languageserver.text.BufferRegistry
|
||||
import org.enso.pkg.PackageManager
|
||||
import org.enso.polyglot.data.TypeGraph
|
||||
import org.enso.polyglot.runtime.Runtime.Api
|
||||
import org.enso.runtimeversionmanager.test.{FakeEnvironment, HasTestDirectory}
|
||||
import org.enso.searcher.sql.{SqlDatabase, SqlSuggestionsRepo, SqlVersionsRepo}
|
||||
import org.enso.testkit.EitherValue
|
||||
import org.enso.text.Sha3_224VersionCalculator
|
||||
import org.scalatest.OptionValues
|
||||
|
||||
import java.nio.file
|
||||
import java.nio.file.Files
|
||||
import java.util.UUID
|
||||
import scala.concurrent.Await
|
||||
import scala.concurrent.duration._
|
||||
|
||||
class BaseServerTest
|
||||
extends JsonRpcServerTestKit
|
||||
with EitherValue
|
||||
with OptionValues {
|
||||
with OptionValues
|
||||
with HasTestDirectory
|
||||
with FakeEnvironment {
|
||||
|
||||
import system.dispatcher
|
||||
|
||||
@ -68,7 +79,12 @@ class BaseServerTest
|
||||
graph
|
||||
}
|
||||
|
||||
private val testDirectory =
|
||||
Files.createTempDirectory("enso-test").toRealPath()
|
||||
override def getTestDirectory: file.Path = testDirectory
|
||||
|
||||
sys.addShutdownHook(FileUtils.deleteQuietly(testContentRoot.file))
|
||||
sys.addShutdownHook(FileUtils.deleteQuietly(testDirectory.toFile))
|
||||
|
||||
def mkConfig: Config =
|
||||
Config(
|
||||
@ -204,30 +220,93 @@ class BaseServerTest
|
||||
Api.VerifyModulesIndexResponse(Seq())
|
||||
)
|
||||
|
||||
locally {
|
||||
val dataRoot = getTestDirectory.resolve("test_data")
|
||||
val editions = dataRoot.resolve("editions")
|
||||
Files.createDirectories(editions)
|
||||
val distribution = file.Path.of("distribution")
|
||||
val currentEdition = buildinfo.Info.currentEdition + ".yaml"
|
||||
val dest = editions.resolve(currentEdition)
|
||||
if (Files.notExists(dest)) {
|
||||
Files.copy(
|
||||
distribution.resolve("editions").resolve(currentEdition),
|
||||
dest
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val environment = fakeInstalledEnvironment()
|
||||
val languageHome = LanguageHome.detectFromExecutableLocation(environment)
|
||||
val distributionManager = new DistributionManager(environment)
|
||||
|
||||
val editionProvider =
|
||||
EditionManager.makeEditionProvider(
|
||||
distributionManager,
|
||||
Some(languageHome)
|
||||
)
|
||||
val editionResolver = EditionResolver(editionProvider)
|
||||
val editionReferenceResolver = new EditionReferenceResolver(
|
||||
config.projectContentRoot.file,
|
||||
editionProvider,
|
||||
editionResolver
|
||||
)
|
||||
val editionManager = EditionManager(distributionManager, Some(languageHome))
|
||||
|
||||
val projectSettingsManager = system.actorOf(
|
||||
ProjectSettingsManager.props(
|
||||
config.projectContentRoot.file,
|
||||
editionResolver
|
||||
)
|
||||
)
|
||||
|
||||
val localLibraryManager = system.actorOf(
|
||||
LocalLibraryManager.props(
|
||||
config.projectContentRoot.file,
|
||||
distributionManager
|
||||
)
|
||||
)
|
||||
|
||||
new JsonConnectionControllerFactory(
|
||||
initializationComponent,
|
||||
bufferRegistry,
|
||||
capabilityRouter,
|
||||
fileManager,
|
||||
contentRootManagerActor,
|
||||
contextRegistry,
|
||||
suggestionsHandler,
|
||||
stdOutController,
|
||||
stdErrController,
|
||||
stdInController,
|
||||
runtimeConnectorProbe.ref,
|
||||
idlenessMonitor,
|
||||
config
|
||||
mainComponent = initializationComponent,
|
||||
bufferRegistry = bufferRegistry,
|
||||
capabilityRouter = capabilityRouter,
|
||||
fileManager = fileManager,
|
||||
contentRootManager = contentRootManagerActor,
|
||||
contextRegistry = contextRegistry,
|
||||
suggestionsHandler = suggestionsHandler,
|
||||
stdOutController = stdOutController,
|
||||
stdErrController = stdErrController,
|
||||
stdInController = stdInController,
|
||||
runtimeConnector = runtimeConnectorProbe.ref,
|
||||
idlenessMonitor = idlenessMonitor,
|
||||
projectSettingsManager = projectSettingsManager,
|
||||
localLibraryManager = localLibraryManager,
|
||||
editionReferenceResolver = editionReferenceResolver,
|
||||
editionManager = editionManager,
|
||||
config = config
|
||||
)
|
||||
}
|
||||
|
||||
def getInitialisedWsClient(): WsTestClient = {
|
||||
val client = new WsTestClient(address)
|
||||
/** Specifies if the `package.yaml` at project root should be auto-created. */
|
||||
protected def initializeProjectPackage: Boolean = true
|
||||
|
||||
lazy val initPackage: Unit = {
|
||||
if (initializeProjectPackage) {
|
||||
PackageManager.Default.create(
|
||||
config.projectContentRoot.file,
|
||||
name = "TestProject"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
def getInitialisedWsClient(debug: Boolean = false): WsTestClient = {
|
||||
val client = new WsTestClient(address, debugMessages = debug)
|
||||
initSession(client)
|
||||
client
|
||||
}
|
||||
|
||||
private def initSession(client: WsTestClient): UUID = {
|
||||
initPackage
|
||||
val clientId = UUID.randomUUID()
|
||||
client.send(json"""
|
||||
{ "jsonrpc": "2.0",
|
||||
@ -252,5 +331,4 @@ class BaseServerTest
|
||||
)
|
||||
clientId
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,245 @@
|
||||
package org.enso.languageserver.websocket.json
|
||||
|
||||
import io.circe.literal._
|
||||
import io.circe.{Json, JsonObject}
|
||||
import org.enso.languageserver.libraries.LibraryEntry
|
||||
import org.enso.languageserver.libraries.LibraryEntry.PublishedLibraryVersion
|
||||
|
||||
class LibrariesTest extends BaseServerTest {
|
||||
"LocalLibraryManager" should {
|
||||
"create a library project and include it on the list of local projects" in {
|
||||
val client = getInitialisedWsClient()
|
||||
client.send(json"""
|
||||
{ "jsonrpc": "2.0",
|
||||
"method": "library/listLocal",
|
||||
"id": 0
|
||||
}
|
||||
""")
|
||||
client.expectJson(json"""
|
||||
{ "jsonrpc": "2.0",
|
||||
"id": 0,
|
||||
"result": {
|
||||
"localLibraries": []
|
||||
}
|
||||
}
|
||||
""")
|
||||
|
||||
client.send(json"""
|
||||
{ "jsonrpc": "2.0",
|
||||
"method": "library/create",
|
||||
"id": 1,
|
||||
"params": {
|
||||
"namespace": "User",
|
||||
"name": "MyLocalLib",
|
||||
"authors": [],
|
||||
"maintainers": [],
|
||||
"license": ""
|
||||
}
|
||||
}
|
||||
""")
|
||||
client.expectJson(json"""
|
||||
{ "jsonrpc": "2.0",
|
||||
"id": 1,
|
||||
"result": null
|
||||
}
|
||||
""")
|
||||
|
||||
client.send(json"""
|
||||
{ "jsonrpc": "2.0",
|
||||
"method": "library/listLocal",
|
||||
"id": 2
|
||||
}
|
||||
""")
|
||||
client.expectJson(json"""
|
||||
{ "jsonrpc": "2.0",
|
||||
"id": 2,
|
||||
"result": {
|
||||
"localLibraries": [
|
||||
{
|
||||
"namespace": "User",
|
||||
"name": "MyLocalLib",
|
||||
"version": {
|
||||
"type": "LocalLibraryVersion"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
""")
|
||||
}
|
||||
|
||||
"fail with LibraryAlreadyExists when creating a library that already " +
|
||||
"existed" ignore {
|
||||
// TODO [RW] error handling (#1877)
|
||||
}
|
||||
}
|
||||
|
||||
"mocked library/preinstall" should {
|
||||
"send progress notifications" in {
|
||||
val client = getInitialisedWsClient()
|
||||
client.send(json"""
|
||||
{ "jsonrpc": "2.0",
|
||||
"method": "library/preinstall",
|
||||
"id": 0,
|
||||
"params": {
|
||||
"namespace": "Foo",
|
||||
"name": "Test"
|
||||
}
|
||||
}
|
||||
""")
|
||||
val messages =
|
||||
for (_ <- 0 to 3) yield {
|
||||
val msg = client.expectSomeJson().asObject.value
|
||||
val method = msg("method").map(_.asString.value).getOrElse("error")
|
||||
val params =
|
||||
msg("params").map(_.asObject.value).getOrElse(JsonObject())
|
||||
(method, params)
|
||||
}
|
||||
|
||||
val taskStart = messages.find(_._1 == "task/started").value
|
||||
val taskId = taskStart._2("taskId").value.asString.value
|
||||
taskStart
|
||||
._2("relatedOperation")
|
||||
.value
|
||||
.asString
|
||||
.value shouldEqual "library/preinstall"
|
||||
|
||||
taskStart._2("unit").value.asString.value shouldEqual "Bytes"
|
||||
|
||||
val updates = messages.filter { case (method, params) =>
|
||||
method == "task/progress-update" &&
|
||||
params("taskId").value.asString.value == taskId
|
||||
}
|
||||
|
||||
updates should not be empty
|
||||
updates.head
|
||||
._2("message")
|
||||
.value
|
||||
.asString
|
||||
.value shouldEqual "Download Test"
|
||||
}
|
||||
}
|
||||
|
||||
"editions/listAvailable" should {
|
||||
"list editions on the search path" in {
|
||||
val client = getInitialisedWsClient()
|
||||
client.send(json"""
|
||||
{ "jsonrpc": "2.0",
|
||||
"method": "editions/listAvailable",
|
||||
"id": 0,
|
||||
"params": {
|
||||
"update": false
|
||||
}
|
||||
}
|
||||
""")
|
||||
client.expectJson(json"""
|
||||
{ "jsonrpc": "2.0",
|
||||
"id": 0,
|
||||
"result": {
|
||||
"editionNames": [
|
||||
${buildinfo.Info.currentEdition}
|
||||
]
|
||||
}
|
||||
}
|
||||
""")
|
||||
}
|
||||
|
||||
"update the list of editions if requested" ignore {
|
||||
// TODO [RW] updating editions
|
||||
}
|
||||
}
|
||||
|
||||
"editions/listDefinedLibraries" should {
|
||||
"include Standard.Base in the list" in {
|
||||
def containsBase(response: Json): Unit = {
|
||||
val result = response.asObject.value("result").value
|
||||
val libs = result.asObject.value("availableLibraries").value
|
||||
val parsed = libs.asArray.value.map(_.as[LibraryEntry])
|
||||
val bases = parsed.collect {
|
||||
case Right(
|
||||
LibraryEntry("Standard", "Base", PublishedLibraryVersion(_, _))
|
||||
) =>
|
||||
()
|
||||
}
|
||||
bases should have size 1
|
||||
}
|
||||
|
||||
val client = getInitialisedWsClient()
|
||||
client.send(json"""
|
||||
{ "jsonrpc": "2.0",
|
||||
"method": "editions/listDefinedLibraries",
|
||||
"id": 0,
|
||||
"params": {
|
||||
"edition": {
|
||||
"type": "CurrentProjectEdition"
|
||||
}
|
||||
}
|
||||
}
|
||||
""")
|
||||
containsBase(client.expectSomeJson())
|
||||
|
||||
val currentEditionName = buildinfo.Info.currentEdition
|
||||
client.send(json"""
|
||||
{ "jsonrpc": "2.0",
|
||||
"method": "editions/listDefinedLibraries",
|
||||
"id": 0,
|
||||
"params": {
|
||||
"edition": {
|
||||
"type": "NamedEdition",
|
||||
"editionName": $currentEditionName
|
||||
}
|
||||
}
|
||||
}
|
||||
""")
|
||||
containsBase(client.expectSomeJson())
|
||||
}
|
||||
}
|
||||
|
||||
"editions/resolve" should {
|
||||
"resolve the engine version associated with an edition" in {
|
||||
val currentVersion = buildinfo.Info.ensoVersion
|
||||
|
||||
val client = getInitialisedWsClient()
|
||||
client.send(json"""
|
||||
{ "jsonrpc": "2.0",
|
||||
"method": "editions/resolve",
|
||||
"id": 0,
|
||||
"params": {
|
||||
"edition": {
|
||||
"type": "CurrentProjectEdition"
|
||||
}
|
||||
}
|
||||
}
|
||||
""")
|
||||
client.expectJson(json"""
|
||||
{ "jsonrpc": "2.0",
|
||||
"id": 0,
|
||||
"result": {
|
||||
"engineVersion": $currentVersion
|
||||
}
|
||||
}
|
||||
""")
|
||||
|
||||
client.send(json"""
|
||||
{ "jsonrpc": "2.0",
|
||||
"method": "editions/resolve",
|
||||
"id": 1,
|
||||
"params": {
|
||||
"edition": {
|
||||
"type": "NamedEdition",
|
||||
"editionName": ${buildinfo.Info.currentEdition}
|
||||
}
|
||||
}
|
||||
}
|
||||
""")
|
||||
client.expectJson(json"""
|
||||
{ "jsonrpc": "2.0",
|
||||
"id": 1,
|
||||
"result": {
|
||||
"engineVersion": $currentVersion
|
||||
}
|
||||
}
|
||||
""")
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,105 @@
|
||||
package org.enso.languageserver.websocket.json
|
||||
|
||||
import io.circe.literal._
|
||||
import org.enso.distribution.FileSystem
|
||||
|
||||
import java.nio.file.Files
|
||||
|
||||
class ProjectSettingsManagerTest extends BaseServerTest {
|
||||
override def beforeAll(): Unit = {
|
||||
super.beforeAll()
|
||||
|
||||
val editionsDir = getTestDirectory.resolve("test_data").resolve("editions")
|
||||
Files.createDirectories(editionsDir)
|
||||
FileSystem.writeTextFile(
|
||||
editionsDir.resolve("some-edition.yaml"),
|
||||
"""engine-version: 1.2.3
|
||||
|""".stripMargin
|
||||
)
|
||||
|
||||
FileSystem.writeTextFile(
|
||||
editionsDir.resolve("broken.yaml"),
|
||||
"""extends: non-existent
|
||||
|""".stripMargin
|
||||
)
|
||||
}
|
||||
|
||||
"ProjectSettingsManager" should {
|
||||
"get default settings" in {
|
||||
val client = getInitialisedWsClient()
|
||||
client.send(json"""
|
||||
{ "jsonrpc": "2.0",
|
||||
"method": "editions/getProjectSettings",
|
||||
"id": 0
|
||||
}
|
||||
""")
|
||||
client.expectJson(json"""
|
||||
{ "jsonrpc": "2.0",
|
||||
"id": 0,
|
||||
"result": {
|
||||
"parentEdition": ${buildinfo.Info.currentEdition},
|
||||
"preferLocalLibraries": true
|
||||
}
|
||||
}
|
||||
""")
|
||||
}
|
||||
|
||||
"allow to set local libraries preference and parent edition and reflect " +
|
||||
"these changes" in {
|
||||
val client = getInitialisedWsClient()
|
||||
client.send(json"""
|
||||
{ "jsonrpc": "2.0",
|
||||
"method": "editions/setProjectLocalLibrariesPreference",
|
||||
"id": 0,
|
||||
"params": {
|
||||
"preferLocalLibraries": false
|
||||
}
|
||||
}
|
||||
""")
|
||||
client.expectJson(json"""
|
||||
{ "jsonrpc": "2.0",
|
||||
"id": 0,
|
||||
"result": {
|
||||
"needsRestart": true
|
||||
}
|
||||
}
|
||||
""")
|
||||
|
||||
client.send(json"""
|
||||
{ "jsonrpc": "2.0",
|
||||
"method": "editions/setParentEdition",
|
||||
"id": 1,
|
||||
"params": {
|
||||
"newEditionName": "some-edition"
|
||||
}
|
||||
}
|
||||
""")
|
||||
client.expectJson(json"""
|
||||
{ "jsonrpc": "2.0",
|
||||
"id": 1,
|
||||
"result": {
|
||||
"needsRestart": true
|
||||
}
|
||||
}
|
||||
""")
|
||||
|
||||
client.send(json"""
|
||||
{ "jsonrpc": "2.0",
|
||||
"method": "editions/getProjectSettings",
|
||||
"id": 2
|
||||
}
|
||||
""")
|
||||
client.expectJson(json"""
|
||||
{ "jsonrpc": "2.0",
|
||||
"id": 2,
|
||||
"result": {
|
||||
"parentEdition": "some-edition",
|
||||
"preferLocalLibraries": false
|
||||
}
|
||||
}
|
||||
""")
|
||||
}
|
||||
|
||||
"fail if the provided parent edition is not resolvable" ignore {}
|
||||
}
|
||||
}
|
@ -9,6 +9,8 @@ import java.io.{File, FileOutputStream}
|
||||
|
||||
class WorkspaceOperationsTest extends BaseServerTest with FlakySpec {
|
||||
|
||||
override def initializeProjectPackage: Boolean = false
|
||||
|
||||
"workspace/projectInfo" must {
|
||||
val packageConfigName = Config.ensoPackageConfigName
|
||||
val testYamlPath = new File(testContentRoot.file, packageConfigName)
|
||||
|
@ -46,9 +46,7 @@ case class Launcher(cliOptions: GlobalCLIOptions) {
|
||||
}
|
||||
private lazy val configurationManager =
|
||||
new GlobalConfigurationManager(componentsManager, distributionManager)
|
||||
private lazy val editionManager = new EditionManager(
|
||||
distributionManager.paths.editionSearchPaths.toList
|
||||
)
|
||||
private lazy val editionManager = EditionManager(distributionManager)
|
||||
private lazy val projectManager = new ProjectManager
|
||||
private lazy val runner =
|
||||
new LauncherRunner(
|
||||
|
@ -31,9 +31,7 @@ class LauncherRunnerSpec extends RuntimeVersionManagerTest {
|
||||
new GlobalConfigurationManager(componentsManager, distributionManager) {
|
||||
override def defaultVersion: SemVer = defaultEngineVersion
|
||||
}
|
||||
val editionManager = new EditionManager(
|
||||
distributionManager.paths.editionSearchPaths.toList
|
||||
)
|
||||
val editionManager = EditionManager(distributionManager)
|
||||
val projectManager = new ProjectManager()
|
||||
val cwd = cwdOverride.getOrElse(getTestDirectory)
|
||||
val runner =
|
||||
|
@ -209,6 +209,10 @@ object Runtime {
|
||||
new JsonSubTypes.Type(
|
||||
value = classOf[Api.LibraryLoaded],
|
||||
name = "libraryLoaded"
|
||||
),
|
||||
new JsonSubTypes.Type(
|
||||
value = classOf[Api.ProgressNotification],
|
||||
name = "progressNotification"
|
||||
)
|
||||
)
|
||||
)
|
||||
@ -1350,6 +1354,40 @@ object Runtime {
|
||||
location: File
|
||||
) extends ApiNotification
|
||||
|
||||
/** A notification containing updates on the progress of long-running tasks.
|
||||
*
|
||||
* @param payload the actual update contained within this notification
|
||||
*/
|
||||
case class ProgressNotification(
|
||||
payload: ProgressNotification.NotificationType
|
||||
) extends ApiNotification
|
||||
|
||||
object ProgressNotification {
|
||||
sealed trait NotificationType
|
||||
|
||||
/** Indicates that a new task has been started. */
|
||||
case class TaskStarted(
|
||||
taskId: UUID,
|
||||
relatedOperation: String,
|
||||
unit: String,
|
||||
total: Option[Long]
|
||||
) extends NotificationType
|
||||
|
||||
/** Indicates that the task has progressed. */
|
||||
case class TaskProgressUpdate(
|
||||
taskId: UUID,
|
||||
message: Option[String],
|
||||
done: Long
|
||||
) extends NotificationType
|
||||
|
||||
/** Indicates that the task has been finished. */
|
||||
case class TaskFinished(
|
||||
taskId: UUID,
|
||||
message: Option[String],
|
||||
success: Boolean
|
||||
) extends NotificationType
|
||||
}
|
||||
|
||||
private lazy val mapper = {
|
||||
val factory = new CBORFactory()
|
||||
val mapper = new ObjectMapper(factory) with ScalaObjectMapper
|
||||
|
@ -67,7 +67,7 @@ object PackageRepository {
|
||||
object Error {
|
||||
|
||||
/** Indicates that a resolution error has happened, for example the package
|
||||
* was not defined in the selected edition.
|
||||
* was not defined in the selected edition.
|
||||
*/
|
||||
case class PackageCouldNotBeResolved(cause: Throwable) extends Error {
|
||||
override def toString: String =
|
||||
@ -95,11 +95,11 @@ object PackageRepository {
|
||||
|
||||
/** The default [[PackageRepository]] implementation.
|
||||
*
|
||||
* @param libraryProvider the [[ResolvingLibraryProvider]] which resolves
|
||||
* which library version should be imported and
|
||||
* locates them (or downloads if they are missing)
|
||||
* @param context the language context
|
||||
* @param builtins the builtins module
|
||||
* @param libraryProvider the [[ResolvingLibraryProvider]] which resolves
|
||||
* which library version should be imported and
|
||||
* locates them (or downloads if they are missing)
|
||||
* @param context the language context
|
||||
* @param builtins the builtins module
|
||||
* @param notificationHandler a notification handler
|
||||
*/
|
||||
class Default(
|
||||
@ -315,12 +315,12 @@ object PackageRepository {
|
||||
* Edition and library search paths are based on the distribution and
|
||||
* language home (if it is provided).
|
||||
*
|
||||
* @param projectPackage the package of the current project (if ran inside of a project)
|
||||
* @param languageHome the language home (if set)
|
||||
* @param projectPackage the package of the current project (if ran inside of a project)
|
||||
* @param languageHome the language home (if set)
|
||||
* @param distributionManager the distribution manager
|
||||
* @param context the context reference, needed to add polyglot libraries to
|
||||
* the classpath
|
||||
* @param builtins the builtins that are always preloaded
|
||||
* @param context the context reference, needed to add polyglot libraries to
|
||||
* the classpath
|
||||
* @param builtins the builtins that are always preloaded
|
||||
* @param notificationHandler a handler for library addition and progress
|
||||
* notifications
|
||||
* @return an initialized [[PackageRepository]]
|
||||
@ -337,11 +337,8 @@ object PackageRepository {
|
||||
.flatMap(_.config.edition)
|
||||
.getOrElse(DefaultEdition.getDefaultEdition)
|
||||
|
||||
val homeManager = languageHome.map { home => LanguageHome(Path.of(home)) }
|
||||
val editionSearchPaths =
|
||||
homeManager.map(_.editions).toList ++
|
||||
distributionManager.paths.editionSearchPaths
|
||||
val editionManager = new EditionManager(editionSearchPaths)
|
||||
val homeManager = languageHome.map { home => LanguageHome(Path.of(home)) }
|
||||
val editionManager = EditionManager(distributionManager, homeManager)
|
||||
val edition = editionManager.resolveEdition(rawEdition).get
|
||||
|
||||
val resolvingLibraryProvider =
|
||||
|
@ -2,7 +2,12 @@ package org.enso.interpreter.instrument
|
||||
|
||||
import com.typesafe.scalalogging.Logger
|
||||
import org.enso.cli.ProgressBar
|
||||
import org.enso.cli.task.{ProgressReporter, TaskProgress}
|
||||
import org.enso.cli.task.{
|
||||
ProgressNotification,
|
||||
ProgressNotificationForwarder,
|
||||
ProgressReporter,
|
||||
TaskProgress
|
||||
}
|
||||
import org.enso.editions.{LibraryName, LibraryVersion}
|
||||
import org.enso.polyglot.runtime.Runtime.{Api, ApiResponse}
|
||||
|
||||
@ -15,9 +20,9 @@ trait NotificationHandler extends ProgressReporter {
|
||||
|
||||
/** Called when a library has been loaded.
|
||||
*
|
||||
* @param libraryName name of the added library
|
||||
* @param libraryName name of the added library
|
||||
* @param libraryVersion selected version
|
||||
* @param location path to the location from which the library is loaded
|
||||
* @param location path to the location from which the library is loaded
|
||||
*/
|
||||
def addedLibrary(
|
||||
libraryName: LibraryName,
|
||||
@ -80,7 +85,9 @@ object NotificationHandler {
|
||||
* notifications to the Language Server, which then should forward them to
|
||||
* the IDE.
|
||||
*/
|
||||
class InteractiveMode(endpoint: Endpoint) extends NotificationHandler {
|
||||
class InteractiveMode(endpoint: Endpoint)
|
||||
extends NotificationHandler
|
||||
with ProgressNotificationForwarder {
|
||||
private val logger = Logger[InteractiveMode]
|
||||
|
||||
private def sendMessage(message: ApiResponse): Unit = {
|
||||
@ -105,7 +112,16 @@ object NotificationHandler {
|
||||
/** @inheritdoc */
|
||||
override def trackProgress(message: String, task: TaskProgress[_]): Unit = {
|
||||
logger.info(message)
|
||||
// TODO [RW] this should be implemented once progress tracking is used by downloads
|
||||
super.trackProgress(message, task)
|
||||
}
|
||||
|
||||
override def sendProgressNotification(
|
||||
notification: ProgressNotification
|
||||
): Unit = sendMessage(
|
||||
ProgressNotificationTranslator.translate(
|
||||
"compiler/downloadingDependencies",
|
||||
notification
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,49 @@
|
||||
package org.enso.interpreter.instrument
|
||||
|
||||
import org.enso.cli.task.{
|
||||
ProgressUnit,
|
||||
ProgressNotification => TaskProgressNotification
|
||||
}
|
||||
import org.enso.polyglot.runtime.Runtime.Api.ProgressNotification
|
||||
import org.enso.polyglot.runtime.Runtime.Api.ProgressNotification._
|
||||
import org.enso.polyglot.runtime.Runtime.ApiResponse
|
||||
|
||||
/** A helper for translating notification formats. */
|
||||
object ProgressNotificationTranslator {
|
||||
|
||||
/** Translates a notification as defined in the CLI module into the format
|
||||
* that is used in the API of the runtime connector, so that it can be
|
||||
* forwarded to the Language Server.
|
||||
*
|
||||
* @param relatedOperationName name of a related operation; these were
|
||||
* originally tied to Project Manager or Language
|
||||
* Server operations, but they can also be based
|
||||
* on internal compiler operations
|
||||
* @param progressNotification the notification to translate
|
||||
*/
|
||||
def translate(
|
||||
relatedOperationName: String,
|
||||
progressNotification: TaskProgressNotification
|
||||
): ApiResponse = {
|
||||
val payload = progressNotification match {
|
||||
case TaskProgressNotification.TaskStarted(taskId, total, unit) =>
|
||||
TaskStarted(
|
||||
taskId = taskId,
|
||||
relatedOperation = relatedOperationName,
|
||||
unit = ProgressUnit.toString(unit),
|
||||
total = total
|
||||
)
|
||||
case TaskProgressNotification.TaskUpdate(taskId, message, done) =>
|
||||
TaskProgressUpdate(taskId, message, done)
|
||||
case TaskProgressNotification.TaskSuccess(taskId) =>
|
||||
TaskFinished(taskId, message = None, success = true)
|
||||
case TaskProgressNotification.TaskFailure(taskId, throwable) =>
|
||||
TaskFinished(
|
||||
taskId,
|
||||
message = Option(throwable.getMessage),
|
||||
success = false
|
||||
)
|
||||
}
|
||||
ProgressNotification(payload)
|
||||
}
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
package org.enso.cli.task
|
||||
|
||||
import java.util.UUID
|
||||
|
||||
/** Internal representation of progress notifications. */
|
||||
sealed trait ProgressNotification
|
||||
object ProgressNotification {
|
||||
|
||||
/** Singals that a new task with progress has been started.
|
||||
*
|
||||
* @param taskId a unique id of the task
|
||||
* @param total the total amount of units that the task is expected to take
|
||||
* @param unit unit of that the progress is reported in
|
||||
*/
|
||||
case class TaskStarted(
|
||||
taskId: UUID,
|
||||
total: Option[Long],
|
||||
unit: ProgressUnit
|
||||
) extends ProgressNotification
|
||||
|
||||
/** Signals an update to task's progress.
|
||||
*
|
||||
* @param taskId the task id
|
||||
* @param message an optional message to display
|
||||
* @param done indication of how much progress has been done since the task
|
||||
* started
|
||||
*/
|
||||
case class TaskUpdate(taskId: UUID, message: Option[String], done: Long)
|
||||
extends ProgressNotification
|
||||
|
||||
/** Signals that a task has been finished successfully.
|
||||
*
|
||||
* @param taskId the task id
|
||||
*/
|
||||
case class TaskSuccess(taskId: UUID) extends ProgressNotification
|
||||
|
||||
/** Signals that a task has failed.
|
||||
*
|
||||
* @param taskId the task id
|
||||
* @param throwable an exception associated with the failure
|
||||
*/
|
||||
case class TaskFailure(taskId: UUID, throwable: Throwable)
|
||||
extends ProgressNotification
|
||||
}
|
@ -0,0 +1,66 @@
|
||||
package org.enso.cli.task
|
||||
|
||||
import java.util.UUID
|
||||
import scala.util.{Failure, Success, Try}
|
||||
|
||||
/** A [[ProgressReporter]] implementation that tracks tasks and sends
|
||||
* [[ProgressNotification]]s using a generic interface.
|
||||
*/
|
||||
trait ProgressNotificationForwarder extends ProgressReporter {
|
||||
|
||||
/** The callback that is used to send the progress notification. */
|
||||
def sendProgressNotification(notification: ProgressNotification): Unit
|
||||
|
||||
/** @inheritdoc */
|
||||
override def trackProgress(message: String, task: TaskProgress[_]): Unit = {
|
||||
var uuid: Option[UUID] = None
|
||||
|
||||
/** Initializes the task on first invocation and just returns the
|
||||
* generated UUID on further invocations.
|
||||
*/
|
||||
def initializeTask(total: Option[Long]): UUID = uuid match {
|
||||
case Some(value) => value
|
||||
case None =>
|
||||
val generated = UUID.randomUUID()
|
||||
uuid = Some(generated)
|
||||
sendProgressNotification(
|
||||
ProgressNotification.TaskStarted(
|
||||
generated,
|
||||
total,
|
||||
task.unit
|
||||
)
|
||||
)
|
||||
generated
|
||||
}
|
||||
|
||||
task.addProgressListener(new ProgressListener[Any] {
|
||||
|
||||
/** @inheritdoc */
|
||||
override def progressUpdate(
|
||||
done: Long,
|
||||
total: Option[Long]
|
||||
): Unit = {
|
||||
val uuid = initializeTask(total)
|
||||
sendProgressNotification(
|
||||
ProgressNotification.TaskUpdate(
|
||||
uuid,
|
||||
Some(message),
|
||||
done
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
override def done(result: Try[Any]): Unit = result match {
|
||||
case Failure(exception) =>
|
||||
val uuid = initializeTask(None)
|
||||
sendProgressNotification(
|
||||
ProgressNotification.TaskFailure(uuid, exception)
|
||||
)
|
||||
case Success(_) =>
|
||||
val uuid = initializeTask(None)
|
||||
sendProgressNotification(ProgressNotification.TaskSuccess(uuid))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -6,8 +6,21 @@ sealed trait ProgressUnit
|
||||
object ProgressUnit {
|
||||
|
||||
/** Specifies that progress amount is measured in bytes. */
|
||||
case object Bytes extends ProgressUnit
|
||||
case object Bytes extends ProgressUnit {
|
||||
override val toString: String = "bytes"
|
||||
}
|
||||
|
||||
/** Does not specify a particular progress unit. */
|
||||
case object Unspecified extends ProgressUnit
|
||||
case object Unspecified extends ProgressUnit {
|
||||
override val toString: String = "unspecified"
|
||||
}
|
||||
|
||||
/** Converts a unit to its string representation. */
|
||||
def toString(unit: ProgressUnit): String = unit.toString
|
||||
|
||||
/** Creates a unit from its string representation, falling back to
|
||||
* [[Unspecified]] if it cannot be recognized.
|
||||
*/
|
||||
def fromString(str: String): ProgressUnit =
|
||||
if (str == Bytes.toString) Bytes else Unspecified
|
||||
}
|
||||
|
@ -1,21 +1,24 @@
|
||||
package org.enso.distribution
|
||||
|
||||
import nl.gn0s1s.bump.SemVer
|
||||
import org.enso.editions
|
||||
import org.enso.editions.provider.FileSystemEditionProvider
|
||||
import org.enso.editions.{
|
||||
DefaultEnsoVersion,
|
||||
EditionResolver,
|
||||
Editions,
|
||||
EnsoVersion
|
||||
}
|
||||
import org.enso.editions.provider.{EditionProvider, FileSystemEditionProvider}
|
||||
import org.enso.editions.{EditionResolver, Editions}
|
||||
|
||||
import java.nio.file.Path
|
||||
import scala.util.{Success, Try}
|
||||
|
||||
/** A helper class for resolving editions. */
|
||||
class EditionManager(searchPaths: List[Path]) {
|
||||
private val editionProvider = FileSystemEditionProvider(searchPaths)
|
||||
import scala.annotation.unused
|
||||
import scala.util.Try
|
||||
|
||||
/** A helper class for resolving editions.
|
||||
*
|
||||
* @param primaryCachePath will be used for updating editions
|
||||
* @param searchPaths all paths to search for editions, should include
|
||||
* [[primaryCachePath]]
|
||||
*/
|
||||
class EditionManager(@unused primaryCachePath: Path, searchPaths: List[Path]) {
|
||||
private val editionProvider = new FileSystemEditionProvider(
|
||||
searchPaths
|
||||
)
|
||||
private val editionResolver = EditionResolver(editionProvider)
|
||||
private val engineVersionResolver =
|
||||
editions.EngineVersionResolver(editionProvider)
|
||||
@ -38,10 +41,46 @@ class EditionManager(searchPaths: List[Path]) {
|
||||
* engine version
|
||||
* @return the resolved engine version
|
||||
*/
|
||||
def resolveEngineVersion(
|
||||
edition: Option[Editions.RawEdition]
|
||||
): Try[EnsoVersion] =
|
||||
edition
|
||||
.map(engineVersionResolver.resolveEnsoVersion(_).toTry)
|
||||
.getOrElse(Success(DefaultEnsoVersion))
|
||||
def resolveEngineVersion(edition: Editions.RawEdition): Try[SemVer] =
|
||||
engineVersionResolver.resolveEnsoVersion(edition).toTry
|
||||
|
||||
// TODO [RW] download edition updates, part of #1772
|
||||
|
||||
/** Find all editions available in the [[searchPaths]]. */
|
||||
def findAllAvailableEditions(): Seq[String] =
|
||||
editionProvider.findAvailableEditions()
|
||||
}
|
||||
|
||||
object EditionManager {
|
||||
|
||||
/** Create an [[EditionProvider]] that can locate editions from the
|
||||
* distribution and the language home.
|
||||
*/
|
||||
def makeEditionProvider(
|
||||
distributionManager: DistributionManager,
|
||||
languageHome: Option[LanguageHome]
|
||||
): EditionProvider = new FileSystemEditionProvider(
|
||||
getSearchPaths(distributionManager, languageHome)
|
||||
)
|
||||
|
||||
/** Get search paths associated with the distribution and language home. */
|
||||
private def getSearchPaths(
|
||||
distributionManager: DistributionManager,
|
||||
languageHome: Option[LanguageHome]
|
||||
): List[Path] = {
|
||||
val paths = languageHome.map(_.editions).toList ++
|
||||
distributionManager.paths.editionSearchPaths
|
||||
paths.distinct
|
||||
}
|
||||
|
||||
/** Create an [[EditionManager]] that can locate editions from the
|
||||
* distribution and the language home.
|
||||
*/
|
||||
def apply(
|
||||
distributionManager: DistributionManager,
|
||||
languageHome: Option[LanguageHome] = None
|
||||
): EditionManager = new EditionManager(
|
||||
distributionManager.paths.cachedEditions,
|
||||
getSearchPaths(distributionManager, languageHome)
|
||||
)
|
||||
}
|
||||
|
@ -20,3 +20,15 @@ case class LanguageHome(languageHome: Path) {
|
||||
def libraries: Path =
|
||||
rootPath.resolve(DistributionManager.LIBRARIES_DIRECTORY)
|
||||
}
|
||||
|
||||
object LanguageHome {
|
||||
|
||||
/** Finds the [[LanguageHome]] based on the path of the runner JAR.
|
||||
*
|
||||
* Only guaranteed to work properly if used in a component that is started by the `engine-runner`.
|
||||
*/
|
||||
def detectFromExecutableLocation(environment: Environment): LanguageHome = {
|
||||
val homePath = environment.getPathToRunningExecutable.getParent
|
||||
LanguageHome(homePath)
|
||||
}
|
||||
}
|
||||
|
@ -29,7 +29,7 @@ object EditionResolutionError {
|
||||
* reference is invalid.
|
||||
*/
|
||||
case class LibraryReferencesUndefinedRepository(
|
||||
libraryName: String,
|
||||
libraryName: LibraryName,
|
||||
repositoryName: String
|
||||
) extends EditionResolutionError(
|
||||
s"A library `$libraryName` references a repository `$repositoryName` " +
|
||||
|
@ -62,16 +62,16 @@ case class EditionResolver(provider: EditionProvider) {
|
||||
* a mapping of resolved libraries
|
||||
*/
|
||||
private def resolveLibraries(
|
||||
libraries: Map[String, Editions.Raw.Library],
|
||||
libraries: Map[LibraryName, Editions.Raw.Library],
|
||||
currentRepositories: Map[String, Editions.Repository],
|
||||
parent: Option[ResolvedEdition]
|
||||
): Either[
|
||||
LibraryReferencesUndefinedRepository,
|
||||
Map[String, Editions.Resolved.Library]
|
||||
Map[LibraryName, Editions.Resolved.Library]
|
||||
] = {
|
||||
val resolvedPairs: Either[
|
||||
LibraryReferencesUndefinedRepository,
|
||||
List[(String, Editions.Resolved.Library)]
|
||||
List[(LibraryName, Editions.Resolved.Library)]
|
||||
] =
|
||||
libraries.toList.traverse { case (name, library) =>
|
||||
val resolved = resolveLibrary(library, currentRepositories, parent)
|
||||
@ -122,7 +122,7 @@ case class EditionResolver(provider: EditionProvider) {
|
||||
case (None, None) =>
|
||||
Left(
|
||||
LibraryReferencesUndefinedRepository(
|
||||
libraryName = library.qualifiedName,
|
||||
libraryName = library.name,
|
||||
repositoryName = repositoryName
|
||||
)
|
||||
)
|
||||
|
@ -48,7 +48,7 @@ object EditionSerialization {
|
||||
implicit val editionDecoder: Decoder[Raw.Edition] = { json =>
|
||||
for {
|
||||
parent <- json.get[Option[EditionName]](Fields.parent)
|
||||
engineVersion <- json.get[Option[EnsoVersion]](Fields.engineVersion)
|
||||
engineVersion <- json.get[Option[SemVer]](Fields.engineVersion)
|
||||
_ <-
|
||||
if (parent.isEmpty && engineVersion.isEmpty)
|
||||
Left(
|
||||
@ -64,7 +64,7 @@ object EditionSerialization {
|
||||
libraries <- json.getOrElse[Seq[Raw.Library]](Fields.libraries)(Seq())
|
||||
res <- {
|
||||
val repositoryMap = Map.from(repositories.map(r => (r.name, r)))
|
||||
val libraryMap = Map.from(libraries.map(l => (l.qualifiedName, l)))
|
||||
val libraryMap = Map.from(libraries.map(l => (l.name, l)))
|
||||
if (libraryMap.size != libraries.size)
|
||||
Left(
|
||||
DecodingFailure(
|
||||
@ -157,7 +157,11 @@ object EditionSerialization {
|
||||
}
|
||||
|
||||
implicit private val libraryDecoder: Decoder[Raw.Library] = { json =>
|
||||
def makeLibrary(name: String, repository: String, version: Option[SemVer]) =
|
||||
def makeLibrary(
|
||||
name: LibraryName,
|
||||
repository: String,
|
||||
version: Option[SemVer]
|
||||
) =
|
||||
if (repository == Fields.localRepositoryName)
|
||||
if (version.isDefined)
|
||||
Left(
|
||||
@ -181,7 +185,7 @@ object EditionSerialization {
|
||||
}
|
||||
}
|
||||
for {
|
||||
name <- json.get[String](Fields.name)
|
||||
name <- json.get[LibraryName](Fields.name)
|
||||
repository <- json.get[String](Fields.repository)
|
||||
version <- json.get[Option[SemVer]](Fields.version)
|
||||
res <- makeLibrary(name, repository, version)
|
||||
|
@ -42,22 +42,22 @@ trait Editions {
|
||||
* It should consist of a prefix followed by a dot an the library name, for
|
||||
* example `Prefix.Library_Name`.
|
||||
*/
|
||||
def qualifiedName: String
|
||||
def name: LibraryName
|
||||
}
|
||||
|
||||
/** Represents a local library. */
|
||||
case class LocalLibrary(override val qualifiedName: String) extends Library
|
||||
case class LocalLibrary(override val name: LibraryName) extends Library
|
||||
|
||||
/** Represents a specific version of the library that is published in a
|
||||
* repository.
|
||||
*
|
||||
* @param qualifiedName the qualified name of the library
|
||||
* @param name the qualified name of the library
|
||||
* @param version the exact version of the library that should be used
|
||||
* @param repository the recommended repository to download the library from,
|
||||
* if it is not yet cached
|
||||
*/
|
||||
case class PublishedLibrary(
|
||||
override val qualifiedName: String,
|
||||
override val name: LibraryName,
|
||||
version: SemVer,
|
||||
repository: LibraryRepositoryType
|
||||
) extends Library
|
||||
@ -75,9 +75,9 @@ trait Editions {
|
||||
*/
|
||||
case class Edition(
|
||||
parent: Option[NestedEditionType] = None,
|
||||
engineVersion: Option[EnsoVersion] = None,
|
||||
engineVersion: Option[SemVer] = None,
|
||||
repositories: Map[String, Editions.Repository] = Map.empty,
|
||||
libraries: Map[String, Library] = Map.empty
|
||||
libraries: Map[LibraryName, Library] = Map.empty
|
||||
) {
|
||||
if (parent.isEmpty && engineVersion.isEmpty)
|
||||
throw new IllegalArgumentException(
|
||||
@ -134,7 +134,7 @@ object Editions {
|
||||
* is either the version override directly specified in the edition or the
|
||||
* version implied by its parent.
|
||||
*/
|
||||
def getEngineVersion: EnsoVersion = edition.engineVersion.getOrElse {
|
||||
def getEngineVersion: SemVer = edition.engineVersion.getOrElse {
|
||||
val parent = edition.parent.getOrElse {
|
||||
throw new IllegalStateException(
|
||||
"Internal error: Resolved edition does not imply an engine version."
|
||||
@ -142,6 +142,23 @@ object Editions {
|
||||
}
|
||||
parent.getEngineVersion
|
||||
}
|
||||
|
||||
/** Returns a mapping of all libraries defined in the edition, including any
|
||||
* libraries defined in parent editions (also taking into account the
|
||||
* overrides).
|
||||
*/
|
||||
def getAllDefinedLibraries: Map[LibraryName, LibraryVersion] = {
|
||||
val parent =
|
||||
edition.parent.map(_.getAllDefinedLibraries).getOrElse(Map.empty)
|
||||
edition.libraries.foldLeft(parent) { case (map, (name, lib)) =>
|
||||
val version = lib match {
|
||||
case Resolved.LocalLibrary(_) => LibraryVersion.Local
|
||||
case Resolved.PublishedLibrary(_, version, repository) =>
|
||||
LibraryVersion.Published(version, repository)
|
||||
}
|
||||
map.updated(name, version)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Syntax helpers for a raw edition. */
|
||||
|
@ -1,5 +1,6 @@
|
||||
package org.enso.editions
|
||||
|
||||
import nl.gn0s1s.bump.SemVer
|
||||
import org.enso.editions.Editions.RawEdition
|
||||
import org.enso.editions.provider.EditionProvider
|
||||
|
||||
@ -19,7 +20,7 @@ case class EngineVersionResolver(editionProvider: EditionProvider) {
|
||||
*/
|
||||
def resolveEnsoVersion(
|
||||
edition: RawEdition
|
||||
): Either[EditionResolutionError, EnsoVersion] = {
|
||||
): Either[EditionResolutionError, SemVer] = {
|
||||
for {
|
||||
edition <- editionResolver.resolve(edition)
|
||||
} yield edition.getEngineVersion
|
||||
|
@ -1,6 +1,7 @@
|
||||
package org.enso.editions
|
||||
|
||||
import io.circe.{Decoder, DecodingFailure}
|
||||
import io.circe.syntax.EncoderOps
|
||||
import io.circe.{Decoder, DecodingFailure, Encoder}
|
||||
|
||||
/** Represents a library name that should uniquely identify the library.
|
||||
*
|
||||
@ -31,6 +32,10 @@ object LibraryName {
|
||||
} yield name
|
||||
}
|
||||
|
||||
implicit val encoder: Encoder[LibraryName] = { libraryName =>
|
||||
libraryName.toString.asJson
|
||||
}
|
||||
|
||||
private val separator = '.'
|
||||
|
||||
/** Creates a [[LibraryName]] from its string representation.
|
||||
|
@ -5,12 +5,14 @@ import org.enso.editions.{EditionSerialization, Editions}
|
||||
import java.io.FileNotFoundException
|
||||
import java.nio.file.{Files, Path}
|
||||
import scala.annotation.tailrec
|
||||
import scala.util.{Failure, Success, Try}
|
||||
import scala.collection.Factory
|
||||
import scala.jdk.StreamConverters.StreamHasToScala
|
||||
import scala.util.{Failure, Success, Try, Using}
|
||||
|
||||
/** An implementation of [[EditionProvider]] that looks for the edition files in
|
||||
* a list of filesystem paths.
|
||||
*/
|
||||
case class FileSystemEditionProvider(searchPaths: List[Path])
|
||||
class FileSystemEditionProvider(searchPaths: List[Path])
|
||||
extends EditionProvider {
|
||||
|
||||
/** @inheritdoc */
|
||||
@ -60,4 +62,23 @@ case class FileSystemEditionProvider(searchPaths: List[Path])
|
||||
.map(EditionReadError)
|
||||
} else Left(EditionNotFound)
|
||||
}
|
||||
|
||||
/** Finds all editions available on the [[searchPaths]]. */
|
||||
def findAvailableEditions(): Seq[String] =
|
||||
searchPaths.flatMap(findEditionsAt).distinct
|
||||
|
||||
private def findEditionName(path: Path): Option[String] = {
|
||||
val name = path.getFileName.toString
|
||||
if (name.endsWith(editionSuffix)) {
|
||||
Some(name.stripSuffix(editionSuffix))
|
||||
} else None
|
||||
}
|
||||
|
||||
private def findEditionsAt(path: Path): Seq[String] =
|
||||
listDir(path).filter(Files.isRegularFile(_)).flatMap(findEditionName)
|
||||
|
||||
private def listDir(dir: Path): Seq[Path] =
|
||||
if (Files.exists(dir))
|
||||
Using(Files.list(dir))(_.toScala(Factory.arrayFactory).toSeq).get
|
||||
else Seq()
|
||||
}
|
||||
|
@ -20,24 +20,28 @@ class EditionResolverSpec
|
||||
val editions: Map[String, Editions.RawEdition] = Map(
|
||||
"2021.0" -> Editions.Raw.Edition(
|
||||
parent = None,
|
||||
engineVersion = Some(SemVerEnsoVersion(SemVer(1, 2, 3))),
|
||||
engineVersion = Some(SemVer(1, 2, 3)),
|
||||
repositories = Map(
|
||||
"main" -> mainRepo
|
||||
),
|
||||
libraries = Map(
|
||||
"Standard.Base" -> Editions.Raw
|
||||
.PublishedLibrary("Standard.Base", SemVer(1, 2, 3), "main")
|
||||
LibraryName("Standard", "Base") -> Editions.Raw
|
||||
.PublishedLibrary(
|
||||
LibraryName("Standard", "Base"),
|
||||
SemVer(1, 2, 3),
|
||||
"main"
|
||||
)
|
||||
)
|
||||
),
|
||||
"cycleA" -> Editions.Raw.Edition(
|
||||
parent = Some("cycleB"),
|
||||
engineVersion = Some(SemVerEnsoVersion(SemVer(1, 2, 3))),
|
||||
engineVersion = Some(SemVer(1, 2, 3)),
|
||||
repositories = Map(),
|
||||
libraries = Map()
|
||||
),
|
||||
"cycleB" -> Editions.Raw.Edition(
|
||||
parent = Some("cycleA"),
|
||||
engineVersion = Some(SemVerEnsoVersion(SemVer(1, 2, 3))),
|
||||
engineVersion = Some(SemVer(1, 2, 3)),
|
||||
repositories = Map(),
|
||||
libraries = Map()
|
||||
)
|
||||
@ -59,12 +63,14 @@ class EditionResolverSpec
|
||||
val repo = Repository.make("foo", "http://example.com").get
|
||||
val edition = Editions.Raw.Edition(
|
||||
parent = None,
|
||||
engineVersion = Some(SemVerEnsoVersion(SemVer(1, 2, 3))),
|
||||
engineVersion = Some(SemVer(1, 2, 3)),
|
||||
repositories = Map("foo" -> repo),
|
||||
libraries = Map(
|
||||
"bar.baz" -> Editions.Raw.LocalLibrary("bar.baz"),
|
||||
"foo.bar" -> Editions.Raw
|
||||
.PublishedLibrary("foo.bar", SemVer(1, 2, 3), "foo")
|
||||
LibraryName("bar", "baz") -> Editions.Raw.LocalLibrary(
|
||||
LibraryName("bar", "baz")
|
||||
),
|
||||
LibraryName("foo", "bar") -> Editions.Raw
|
||||
.PublishedLibrary(LibraryName("foo", "bar"), SemVer(1, 2, 3), "foo")
|
||||
)
|
||||
)
|
||||
|
||||
@ -73,33 +79,47 @@ class EditionResolverSpec
|
||||
resolved.parent should be(empty)
|
||||
resolved.repositories shouldEqual edition.repositories
|
||||
resolved.libraries should have size 2
|
||||
resolved.libraries("bar.baz") shouldEqual Editions.Resolved
|
||||
.LocalLibrary("bar.baz")
|
||||
resolved.libraries("foo.bar") shouldEqual Editions.Resolved
|
||||
.PublishedLibrary("foo.bar", SemVer(1, 2, 3), repo)
|
||||
resolved.libraries(
|
||||
LibraryName("bar", "baz")
|
||||
) shouldEqual Editions.Resolved
|
||||
.LocalLibrary(LibraryName("bar", "baz"))
|
||||
resolved.libraries(
|
||||
LibraryName("foo", "bar")
|
||||
) shouldEqual Editions.Resolved
|
||||
.PublishedLibrary(LibraryName("foo", "bar"), SemVer(1, 2, 3), repo)
|
||||
}
|
||||
}
|
||||
|
||||
"resolve a nested edition" in {
|
||||
val edition = Editions.Raw.Edition(
|
||||
parent = Some("2021.0"),
|
||||
engineVersion = Some(SemVerEnsoVersion(SemVer(1, 2, 3))),
|
||||
engineVersion = Some(SemVer(1, 2, 3)),
|
||||
repositories = Map(),
|
||||
libraries = Map(
|
||||
"bar.baz" -> Editions.Raw.LocalLibrary("bar.baz"),
|
||||
"foo.bar" -> Editions.Raw
|
||||
.PublishedLibrary("foo.bar", SemVer(1, 2, 3), "main")
|
||||
LibraryName("bar", "baz") -> Editions.Raw.LocalLibrary(
|
||||
LibraryName("bar", "baz")
|
||||
),
|
||||
LibraryName("foo", "bar") -> Editions.Raw
|
||||
.PublishedLibrary(
|
||||
LibraryName("foo", "bar"),
|
||||
SemVer(1, 2, 3),
|
||||
"main"
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
inside(resolver.resolve(edition)) { case Right(resolved) =>
|
||||
resolved.parent should be(defined)
|
||||
resolved.libraries should have size 2
|
||||
resolved.libraries("bar.baz") shouldEqual Editions.Resolved
|
||||
.LocalLibrary("bar.baz")
|
||||
resolved.libraries("foo.bar") shouldEqual Editions.Resolved
|
||||
resolved.libraries(
|
||||
LibraryName("bar", "baz")
|
||||
) shouldEqual Editions.Resolved
|
||||
.LocalLibrary(LibraryName("bar", "baz"))
|
||||
resolved.libraries(
|
||||
LibraryName("foo", "bar")
|
||||
) shouldEqual Editions.Resolved
|
||||
.PublishedLibrary(
|
||||
"foo.bar",
|
||||
LibraryName("foo", "bar"),
|
||||
SemVer(1, 2, 3),
|
||||
FakeEditionProvider.mainRepo
|
||||
)
|
||||
@ -116,24 +136,30 @@ class EditionResolverSpec
|
||||
engineVersion = None,
|
||||
repositories = Map("main" -> localRepo),
|
||||
libraries = Map(
|
||||
"foo.bar" -> Editions.Raw
|
||||
.PublishedLibrary("foo.bar", SemVer(1, 2, 3), "main")
|
||||
LibraryName("foo", "bar") -> Editions.Raw
|
||||
.PublishedLibrary(
|
||||
LibraryName("foo", "bar"),
|
||||
SemVer(1, 2, 3),
|
||||
"main"
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
inside(resolver.resolve(edition)) { case Right(resolved) =>
|
||||
resolved.parent should be(defined)
|
||||
resolved.libraries should have size 1
|
||||
resolved.libraries("foo.bar") shouldEqual
|
||||
resolved.libraries(LibraryName("foo", "bar")) shouldEqual
|
||||
Editions.Resolved.PublishedLibrary(
|
||||
"foo.bar",
|
||||
LibraryName("foo", "bar"),
|
||||
SemVer(1, 2, 3),
|
||||
localRepo
|
||||
)
|
||||
|
||||
resolved.parent.value.libraries("Standard.Base") shouldEqual
|
||||
resolved.parent.value.libraries(
|
||||
LibraryName("Standard", "Base")
|
||||
) shouldEqual
|
||||
Editions.Resolved.PublishedLibrary(
|
||||
"Standard.Base",
|
||||
LibraryName("Standard", "Base"),
|
||||
SemVer(1, 2, 3),
|
||||
FakeEditionProvider.mainRepo
|
||||
)
|
||||
@ -143,7 +169,7 @@ class EditionResolverSpec
|
||||
"avoid cycles in the resolution" in {
|
||||
val edition = Editions.Raw.Edition(
|
||||
parent = Some("cycleA"),
|
||||
engineVersion = Some(SemVerEnsoVersion(SemVer(1, 2, 3))),
|
||||
engineVersion = Some(SemVer(1, 2, 3)),
|
||||
repositories = Map(),
|
||||
libraries = Map()
|
||||
)
|
||||
|
@ -54,13 +54,16 @@ class EditionSerializationSpec extends AnyWordSpec with Matchers with Inside {
|
||||
.url shouldEqual "http://127.0.0.1:8080/root"
|
||||
|
||||
edition.libraries.values should contain theSameElementsAs Seq(
|
||||
Editions.Raw.LocalLibrary("Foo.Local"),
|
||||
Editions.Raw.PublishedLibrary("Bar.Baz", SemVer(0, 0, 0), "example"),
|
||||
Editions.Raw.PublishedLibrary("A.B", SemVer(1, 0, 1), "bar")
|
||||
)
|
||||
edition.engineVersion should contain(
|
||||
SemVerEnsoVersion(SemVer(1, 2, 3, Some("SNAPSHOT")))
|
||||
Editions.Raw.LocalLibrary(LibraryName("Foo", "Local")),
|
||||
Editions.Raw.PublishedLibrary(
|
||||
LibraryName("Bar", "Baz"),
|
||||
SemVer(0, 0, 0),
|
||||
"example"
|
||||
),
|
||||
Editions.Raw
|
||||
.PublishedLibrary(LibraryName("A", "B"), SemVer(1, 0, 1), "bar")
|
||||
)
|
||||
edition.engineVersion should contain(SemVer(1, 2, 3, Some("SNAPSHOT")))
|
||||
}
|
||||
}
|
||||
|
||||
@ -82,7 +85,7 @@ class EditionSerializationSpec extends AnyWordSpec with Matchers with Inside {
|
||||
val parsed = EditionSerialization.parseYamlString(
|
||||
"""extends: foo
|
||||
|libraries:
|
||||
|- name: bar
|
||||
|- name: bar.baz
|
||||
| repository: local
|
||||
| version: 1.2.3-SHOULD-NOT-BE-HERE
|
||||
|""".stripMargin
|
||||
@ -94,7 +97,7 @@ class EditionSerializationSpec extends AnyWordSpec with Matchers with Inside {
|
||||
val parsed2 = EditionSerialization.parseYamlString(
|
||||
"""extends: foo
|
||||
|libraries:
|
||||
|- name: bar
|
||||
|- name: bar.baz
|
||||
| repository: something
|
||||
|""".stripMargin
|
||||
)
|
||||
|
@ -118,6 +118,11 @@ abstract class JsonRpcServerTestKit
|
||||
parsed shouldEqual Right(json)
|
||||
}
|
||||
|
||||
def expectSomeJson(timeout: FiniteDuration = 5.seconds.dilated): Json = {
|
||||
val parsed = parse(expectMessage(timeout))
|
||||
inside(parsed) { case Right(json) => json }
|
||||
}
|
||||
|
||||
def expectNoMessage(): Unit = outActor.expectNoMessage()
|
||||
}
|
||||
}
|
||||
|
@ -54,7 +54,7 @@ case class LibraryResolver(
|
||||
): Either[LibraryResolutionError, LibraryVersion] = {
|
||||
import Editions.Resolved._
|
||||
val immediateResult =
|
||||
edition.libraries.get(libraryName.qualifiedName).map {
|
||||
edition.libraries.get(libraryName).map {
|
||||
case LocalLibrary(_) =>
|
||||
Right(LibraryVersion.Local)
|
||||
case PublishedLibrary(_, version, repository) =>
|
||||
|
@ -1,7 +1,6 @@
|
||||
package org.enso.librarymanager.local
|
||||
|
||||
import com.typesafe.scalalogging.Logger
|
||||
import org.enso.distribution.FileSystem.PathSyntax
|
||||
import org.enso.editions.LibraryName
|
||||
import org.enso.logger.masking.MaskedPath
|
||||
|
||||
@ -31,7 +30,8 @@ class DefaultLocalLibraryProvider(searchPaths: List[Path])
|
||||
searchPaths: List[Path]
|
||||
): Option[Path] = searchPaths match {
|
||||
case head :: tail =>
|
||||
val potentialPath = head / libraryName.namespace / libraryName.name
|
||||
val potentialPath =
|
||||
LocalLibraryProvider.resolveLibraryPath(head, libraryName)
|
||||
if (Files.exists(potentialPath) && Files.isDirectory(potentialPath)) {
|
||||
logger.trace(
|
||||
s"Found a local $libraryName at " +
|
||||
|
@ -1,5 +1,6 @@
|
||||
package org.enso.librarymanager.local
|
||||
|
||||
import org.enso.distribution.FileSystem.PathSyntax
|
||||
import org.enso.editions.LibraryName
|
||||
|
||||
import java.nio.file.Path
|
||||
@ -12,3 +13,12 @@ trait LocalLibraryProvider {
|
||||
*/
|
||||
def findLibrary(libraryName: LibraryName): Option[Path]
|
||||
}
|
||||
|
||||
object LocalLibraryProvider {
|
||||
|
||||
/** Resolve a path to the package root of a particular library located in one
|
||||
* of the local library roots.
|
||||
*/
|
||||
def resolveLibraryPath(root: Path, libraryName: LibraryName): Path =
|
||||
root / libraryName.namespace / libraryName.name
|
||||
}
|
||||
|
@ -2,12 +2,7 @@ package org.enso.librarymanager
|
||||
|
||||
import nl.gn0s1s.bump.SemVer
|
||||
import org.enso.editions.Editions.Repository
|
||||
import org.enso.editions.{
|
||||
DefaultEnsoVersion,
|
||||
Editions,
|
||||
LibraryName,
|
||||
LibraryVersion
|
||||
}
|
||||
import org.enso.editions.{Editions, LibraryName, LibraryVersion}
|
||||
import org.enso.librarymanager.local.LocalLibraryProvider
|
||||
import org.enso.testkit.EitherValue
|
||||
import org.scalatest.Inside
|
||||
@ -25,11 +20,15 @@ class LibraryResolverSpec
|
||||
val mainRepo = Repository.make("main", "https://example.com/main").get
|
||||
val parentEdition = Editions.Resolved.Edition(
|
||||
parent = None,
|
||||
engineVersion = Some(DefaultEnsoVersion),
|
||||
engineVersion = Some(SemVer(0, 0, 0)),
|
||||
repositories = Map("main" -> mainRepo),
|
||||
libraries = Map(
|
||||
"Standard.Base" -> Editions.Resolved
|
||||
.PublishedLibrary("Standard.Base", SemVer(4, 5, 6), mainRepo)
|
||||
LibraryName("Standard", "Base") -> Editions.Resolved
|
||||
.PublishedLibrary(
|
||||
LibraryName("Standard", "Base"),
|
||||
SemVer(4, 5, 6),
|
||||
mainRepo
|
||||
)
|
||||
)
|
||||
)
|
||||
val customRepo = Repository.make("custom", "https://example.com/custom").get
|
||||
@ -38,24 +37,34 @@ class LibraryResolverSpec
|
||||
engineVersion = None,
|
||||
repositories = Map("custom" -> customRepo),
|
||||
libraries = Map(
|
||||
"Foo.Main" -> Editions.Resolved
|
||||
.PublishedLibrary("Foo.Main", SemVer(1, 0, 0), mainRepo),
|
||||
"Foo.My" -> Editions.Resolved
|
||||
.PublishedLibrary("Foo.My", SemVer(2, 0, 0), customRepo),
|
||||
"Foo.Local" -> Editions.Resolved.LocalLibrary("Foo.Local")
|
||||
LibraryName("Foo", "Main") -> Editions.Resolved
|
||||
.PublishedLibrary(
|
||||
LibraryName("Foo", "Main"),
|
||||
SemVer(1, 0, 0),
|
||||
mainRepo
|
||||
),
|
||||
LibraryName("Foo", "My") -> Editions.Resolved
|
||||
.PublishedLibrary(
|
||||
LibraryName("Foo", "My"),
|
||||
SemVer(2, 0, 0),
|
||||
customRepo
|
||||
),
|
||||
LibraryName("Foo", "Local") -> Editions.Resolved.LocalLibrary(
|
||||
LibraryName("Foo", "Local")
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
case class FakeLocalLibraryProvider(fixtures: Map[String, Path])
|
||||
case class FakeLocalLibraryProvider(fixtures: Map[LibraryName, Path])
|
||||
extends LocalLibraryProvider {
|
||||
override def findLibrary(libraryName: LibraryName): Option[Path] =
|
||||
fixtures.get(libraryName.qualifiedName)
|
||||
fixtures.get(libraryName)
|
||||
}
|
||||
|
||||
val localLibraries = Map(
|
||||
"Foo.My" -> Path.of("./Foo/My"),
|
||||
"Foo.Local" -> Path.of("./Foo/Local"),
|
||||
"Standard.Base" -> Path.of("./Standard/Base")
|
||||
LibraryName("Foo", "My") -> Path.of("./Foo/My"),
|
||||
LibraryName("Foo", "Local") -> Path.of("./Foo/Local"),
|
||||
LibraryName("Standard", "Base") -> Path.of("./Standard/Base")
|
||||
)
|
||||
|
||||
val resolver = LibraryResolver(FakeLocalLibraryProvider(localLibraries))
|
||||
|
@ -205,7 +205,26 @@ object Config {
|
||||
val overridesObject = JsonObject(
|
||||
overrides ++ preferLocalOverride: _*
|
||||
)
|
||||
originals.remove(JsonFields.ensoVersion).deepMerge(overridesObject).asJson
|
||||
|
||||
/** Fields that should not be inherited from the original set of fields.
|
||||
*
|
||||
* `ensoVersion` is dropped, because due to migration it is overridden by
|
||||
* `edition` and we don't want to have both to avoid inconsistency.
|
||||
*
|
||||
* `prefer-local-libraries` cannot be inherited, because if it was set to
|
||||
* `true` and we have changed it to `false`, overrides will not include it,
|
||||
* because, as `false` is its default value, we just ignore the field. But
|
||||
* if we inherit it from original fields, we would get `true` back. If the
|
||||
* setting is still set to true, it will be included in the overrides, so
|
||||
* it does not have to be inherited either.
|
||||
*/
|
||||
val fieldsToRemoveFromOriginals =
|
||||
Seq(JsonFields.ensoVersion, JsonFields.preferLocalLibraries)
|
||||
|
||||
val removed = fieldsToRemoveFromOriginals.foldLeft(originals) {
|
||||
case (obj, key) => obj.remove(key)
|
||||
}
|
||||
removed.deepMerge(overridesObject).asJson
|
||||
}
|
||||
|
||||
/** Tries to parse the [[Config]] from a YAML string. */
|
||||
@ -229,7 +248,7 @@ object Config {
|
||||
ensoVersion: SemVer
|
||||
): Editions.RawEdition = Editions.Raw.Edition(
|
||||
parent = None,
|
||||
engineVersion = Some(SemVerEnsoVersion(ensoVersion)),
|
||||
engineVersion = Some(ensoVersion),
|
||||
repositories = Map(),
|
||||
libraries = Map()
|
||||
)
|
||||
|
@ -48,11 +48,13 @@ case class Package[F](
|
||||
/** Stores the package metadata on the hard drive. If the package does not exist,
|
||||
* creates the required directory structure.
|
||||
*/
|
||||
def save(): Unit = {
|
||||
if (!root.exists) createDirectories()
|
||||
if (!sourceDir.exists) createSourceDir()
|
||||
saveConfig()
|
||||
}
|
||||
def save(): Try[Unit] = for {
|
||||
_ <- Try {
|
||||
if (!root.exists) createDirectories()
|
||||
if (!sourceDir.exists) createSourceDir()
|
||||
}
|
||||
_ <- saveConfig()
|
||||
} yield ()
|
||||
|
||||
/** Creates the package directory structure.
|
||||
*/
|
||||
@ -97,11 +99,10 @@ case class Package[F](
|
||||
|
||||
/** Saves the config metadata into the package configuration file.
|
||||
*/
|
||||
def saveConfig(): Unit = {
|
||||
val writer = configFile.newBufferedWriter
|
||||
Try(writer.write(config.toYaml))
|
||||
writer.close()
|
||||
}
|
||||
def saveConfig(): Try[Unit] =
|
||||
Using(configFile.newBufferedWriter) { writer =>
|
||||
writer.write(config.toYaml)
|
||||
}
|
||||
|
||||
/** Gets the location of the package's Main file.
|
||||
*
|
||||
@ -208,13 +209,14 @@ class PackageManager[F](implicit val fileSystem: FileSystem[F]) {
|
||||
version: String = "0.0.1",
|
||||
edition: Option[Editions.RawEdition] = None,
|
||||
authors: List[Contact] = List(),
|
||||
maintainers: List[Contact] = List()
|
||||
maintainers: List[Contact] = List(),
|
||||
license: String = ""
|
||||
): Package[F] = {
|
||||
val config = Config(
|
||||
name = NameValidation.normalizeName(name),
|
||||
namespace = namespace,
|
||||
version = version,
|
||||
license = "",
|
||||
license = license,
|
||||
authors = authors,
|
||||
edition = edition,
|
||||
preferLocalLibraries = true,
|
||||
|
@ -2,7 +2,6 @@ package org.enso.pkg
|
||||
|
||||
import io.circe.{Json, JsonObject}
|
||||
import nl.gn0s1s.bump.SemVer
|
||||
import org.enso.editions.SemVerEnsoVersion
|
||||
import org.scalatest.matchers.should.Matchers
|
||||
import org.scalatest.wordspec.AnyWordSpec
|
||||
import org.scalatest.{Inside, OptionValues}
|
||||
@ -64,9 +63,7 @@ class ConfigSpec
|
||||
|""".stripMargin
|
||||
val parsed = Config.fromYaml(oldFormat).get
|
||||
|
||||
parsed.edition.get.engineVersion should contain(
|
||||
SemVerEnsoVersion(SemVer(1, 2, 3))
|
||||
)
|
||||
parsed.edition.get.engineVersion should contain(SemVer(1, 2, 3))
|
||||
|
||||
val serialized = parsed.toYaml
|
||||
val parsedAgain = Config.fromYaml(serialized).get
|
||||
|
@ -1,26 +0,0 @@
|
||||
package org.enso.projectmanager.data
|
||||
|
||||
import enumeratum._
|
||||
import org.enso.cli.task.{TaskProgress, ProgressUnit => TaskProgressUnit}
|
||||
|
||||
/** Represents the unit used by progress updates. */
|
||||
sealed trait ProgressUnit extends EnumEntry
|
||||
object ProgressUnit extends Enum[ProgressUnit] with CirceEnum[ProgressUnit] {
|
||||
|
||||
/** Indicates that progress is measured by amount of bytes processed. */
|
||||
case object Bytes extends ProgressUnit
|
||||
|
||||
/** Indicates that progress is measured by some other unit or it is not
|
||||
* measured at all.
|
||||
*/
|
||||
case object Other extends ProgressUnit
|
||||
|
||||
override val values = findValues
|
||||
|
||||
/** Creates a [[ProgressUnit]] from the unit associated with [[TaskProgress]].
|
||||
*/
|
||||
def fromTask(task: TaskProgress[_]): ProgressUnit = task.unit match {
|
||||
case TaskProgressUnit.Bytes => Bytes
|
||||
case TaskProgressUnit.Unspecified => Other
|
||||
}
|
||||
}
|
@ -3,6 +3,7 @@ package org.enso.projectmanager.protocol
|
||||
import io.circe.generic.auto._
|
||||
import org.enso.jsonrpc.Protocol
|
||||
import org.enso.projectmanager.protocol.ProjectManagementApi._
|
||||
import org.enso.cli.task.notifications.TaskNotificationApi._
|
||||
|
||||
/** Implicits from this module are required for correct serialization.
|
||||
*
|
||||
|
@ -9,7 +9,6 @@ import org.enso.jsonrpc.{Error, HasParams, HasResult, Method, Unused}
|
||||
import org.enso.projectmanager.data.{
|
||||
EngineVersion,
|
||||
MissingComponentAction,
|
||||
ProgressUnit,
|
||||
ProjectMetadata,
|
||||
Socket
|
||||
}
|
||||
@ -117,46 +116,6 @@ object ProjectManagementApi {
|
||||
}
|
||||
}
|
||||
|
||||
case object TaskStarted extends Method("task/started") {
|
||||
|
||||
case class Params(
|
||||
taskId: UUID,
|
||||
relatedOperation: String,
|
||||
unit: ProgressUnit,
|
||||
total: Option[Long]
|
||||
)
|
||||
|
||||
implicit val hasParams = new HasParams[this.type] {
|
||||
type Params = TaskStarted.Params
|
||||
}
|
||||
}
|
||||
|
||||
case object TaskProgressUpdate extends Method("task/progress-update") {
|
||||
|
||||
case class Params(
|
||||
taskId: UUID,
|
||||
message: Option[String],
|
||||
done: Long
|
||||
)
|
||||
|
||||
implicit val hasParams = new HasParams[this.type] {
|
||||
type Params = TaskProgressUpdate.Params
|
||||
}
|
||||
}
|
||||
|
||||
case object TaskFinished extends Method("task/progress-update") {
|
||||
|
||||
case class Params(
|
||||
taskId: UUID,
|
||||
message: Option[String],
|
||||
success: Boolean
|
||||
)
|
||||
|
||||
implicit val hasParams = new HasParams[this.type] {
|
||||
type Params = TaskFinished.Params
|
||||
}
|
||||
}
|
||||
|
||||
case object EngineListInstalled extends Method("engine/list-installed") {
|
||||
|
||||
case class Result(versions: Seq[EngineVersion])
|
||||
|
@ -3,19 +3,11 @@ package org.enso.projectmanager.requesthandler
|
||||
import akka.actor.{Actor, ActorRef, Cancellable, Stash, Status}
|
||||
import akka.pattern.pipe
|
||||
import com.typesafe.scalalogging.{LazyLogging, Logger}
|
||||
import org.enso.cli.task.ProgressNotification
|
||||
import org.enso.cli.task.notifications.ActorProgressNotificationForwarder
|
||||
import org.enso.jsonrpc.Errors.ServiceError
|
||||
import org.enso.jsonrpc.{
|
||||
HasParams,
|
||||
HasResult,
|
||||
Id,
|
||||
Method,
|
||||
Request,
|
||||
ResponseError,
|
||||
ResponseResult
|
||||
}
|
||||
import org.enso.jsonrpc._
|
||||
import org.enso.projectmanager.control.effect.Exec
|
||||
import org.enso.projectmanager.service.versionmanagement.ProgressNotification
|
||||
import org.enso.projectmanager.service.versionmanagement.ProgressNotification.translateProgressNotification
|
||||
import org.enso.projectmanager.util.UnhandledLogging
|
||||
|
||||
import scala.annotation.unused
|
||||
@ -118,7 +110,8 @@ abstract class RequestHandler[
|
||||
abandonTimeout(id, replyTo, timeoutCancellable)
|
||||
case _ =>
|
||||
}
|
||||
replyTo ! translateProgressNotification(method.name, notification)
|
||||
replyTo ! ActorProgressNotificationForwarder
|
||||
.translateProgressNotification(method.name, notification)
|
||||
}
|
||||
|
||||
/** Cancels the timeout operation.
|
||||
|
@ -4,7 +4,7 @@ import akka.actor.ActorRef
|
||||
import cats.MonadError
|
||||
import com.typesafe.scalalogging.Logger
|
||||
import nl.gn0s1s.bump.SemVer
|
||||
import org.enso.editions.EnsoVersion
|
||||
import org.enso.editions.DefaultEdition
|
||||
import org.enso.pkg.Config
|
||||
import org.enso.projectmanager.control.core.syntax._
|
||||
import org.enso.projectmanager.control.core.{
|
||||
@ -44,7 +44,6 @@ import org.enso.projectmanager.service.ValidationFailure.{
|
||||
NameShouldStartWithCapitalLetter
|
||||
}
|
||||
import org.enso.projectmanager.service.config.GlobalConfigServiceApi
|
||||
import org.enso.projectmanager.service.config.GlobalConfigServiceFailure.ConfigurationFileAccessFailure
|
||||
import org.enso.projectmanager.service.versionmanagement.RuntimeVersionManagerErrorRecoverySyntax._
|
||||
import org.enso.projectmanager.service.versionmanagement.RuntimeVersionManagerFactory
|
||||
import org.enso.projectmanager.versionmanagement.DistributionConfiguration
|
||||
@ -306,15 +305,7 @@ class ProjectService[
|
||||
missingComponentAction: MissingComponentAction
|
||||
): F[ProjectServiceFailure, RunningLanguageServerInfo] = for {
|
||||
version <- resolveProjectVersion(project)
|
||||
version <- configurationService
|
||||
.resolveEnsoVersion(version)
|
||||
.mapError { case ConfigurationFileAccessFailure(message) =>
|
||||
ProjectOpenFailed(
|
||||
"Could not deduce the default version to use for the project: " +
|
||||
message
|
||||
)
|
||||
}
|
||||
_ <- preinstallEngine(progressTracker, version, missingComponentAction)
|
||||
_ <- preinstallEngine(progressTracker, version, missingComponentAction)
|
||||
sockets <- languageServerGateway
|
||||
.start(progressTracker, clientId, project, version)
|
||||
.mapError {
|
||||
@ -371,18 +362,7 @@ class ProjectService[
|
||||
private def resolveProjectMetadata(
|
||||
project: Project
|
||||
): F[ProjectServiceFailure, ProjectMetadata] = {
|
||||
val version = for {
|
||||
version <- resolveProjectVersion(project)
|
||||
version <- configurationService
|
||||
.resolveEnsoVersion(version)
|
||||
.mapError { case ConfigurationFileAccessFailure(message) =>
|
||||
GlobalConfigurationAccessFailure(
|
||||
"Could not deduce the default version to use for the project: " +
|
||||
message
|
||||
)
|
||||
}
|
||||
} yield version
|
||||
|
||||
val version = resolveProjectVersion(project)
|
||||
for {
|
||||
version <- version.map(Some(_)).recover { error =>
|
||||
// TODO [RW] We may consider sending this warning to the IDE once
|
||||
@ -483,11 +463,16 @@ class ProjectService[
|
||||
|
||||
private def resolveProjectVersion(
|
||||
project: Project
|
||||
): F[ProjectServiceFailure, EnsoVersion] =
|
||||
): F[ProjectServiceFailure, SemVer] =
|
||||
Sync[F]
|
||||
.blockingOp {
|
||||
// TODO [RW] at some point we will need to use the configuration service to get the actual default version, see #1864
|
||||
val _ = configurationService
|
||||
|
||||
val edition =
|
||||
project.edition.getOrElse(DefaultEdition.getDefaultEdition)
|
||||
distributionConfiguration.editionManager
|
||||
.resolveEngineVersion(project.edition)
|
||||
.resolveEngineVersion(edition)
|
||||
.get
|
||||
}
|
||||
.mapError { error =>
|
||||
|
@ -1,18 +1,20 @@
|
||||
package org.enso.projectmanager.service.versionmanagement
|
||||
|
||||
import java.util.UUID
|
||||
import akka.actor.ActorRef
|
||||
import com.typesafe.scalalogging.Logger
|
||||
import nl.gn0s1s.bump.SemVer
|
||||
import org.enso.cli.task.{ProgressListener, TaskProgress}
|
||||
import org.enso.cli.task.{
|
||||
ProgressNotification,
|
||||
ProgressNotificationForwarder,
|
||||
ProgressUnit
|
||||
}
|
||||
import org.enso.distribution.locking.Resource
|
||||
import org.enso.projectmanager.data.ProgressUnit
|
||||
import org.enso.runtimeversionmanager.components.{
|
||||
GraalVMVersion,
|
||||
RuntimeVersionManagementUserInterface
|
||||
}
|
||||
|
||||
import scala.util.{Failure, Success, Try}
|
||||
import java.util.UUID
|
||||
|
||||
/** A [[RuntimeVersionManagementUserInterface]] that sends
|
||||
* [[ProgressNotification]] to the specified actors (both for usual tasks and
|
||||
@ -27,54 +29,8 @@ class ControllerInterface(
|
||||
progressTracker: ActorRef,
|
||||
allowMissingComponents: Boolean,
|
||||
allowBrokenComponents: Boolean
|
||||
) extends RuntimeVersionManagementUserInterface {
|
||||
|
||||
/** @inheritdoc */
|
||||
override def trackProgress(message: String, task: TaskProgress[_]): Unit = {
|
||||
var uuid: Option[UUID] = None
|
||||
|
||||
/** Initializes the task on first invocation and just returns the
|
||||
* generated UUID on further invocations.
|
||||
*/
|
||||
def initializeTask(total: Option[Long]): UUID = uuid match {
|
||||
case Some(value) => value
|
||||
case None =>
|
||||
val generated = UUID.randomUUID()
|
||||
uuid = Some(generated)
|
||||
val unit = ProgressUnit.fromTask(task)
|
||||
progressTracker ! ProgressNotification.TaskStarted(
|
||||
generated,
|
||||
total,
|
||||
unit
|
||||
)
|
||||
generated
|
||||
}
|
||||
task.addProgressListener(new ProgressListener[Any] {
|
||||
|
||||
/** @inheritdoc */
|
||||
override def progressUpdate(
|
||||
done: Long,
|
||||
total: Option[Long]
|
||||
): Unit = {
|
||||
val uuid = initializeTask(total)
|
||||
progressTracker ! ProgressNotification.TaskUpdate(
|
||||
uuid,
|
||||
Some(message),
|
||||
done
|
||||
)
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
override def done(result: Try[Any]): Unit = result match {
|
||||
case Failure(exception) =>
|
||||
val uuid = initializeTask(None)
|
||||
progressTracker ! ProgressNotification.TaskFailure(uuid, exception)
|
||||
case Success(_) =>
|
||||
val uuid = initializeTask(None)
|
||||
progressTracker ! ProgressNotification.TaskSuccess(uuid)
|
||||
}
|
||||
})
|
||||
}
|
||||
) extends RuntimeVersionManagementUserInterface
|
||||
with ProgressNotificationForwarder {
|
||||
|
||||
/** @inheritdoc */
|
||||
override def shouldInstallMissingEngine(version: SemVer): Boolean =
|
||||
@ -101,7 +57,7 @@ class ControllerInterface(
|
||||
progressTracker ! ProgressNotification.TaskStarted(
|
||||
uuid,
|
||||
None,
|
||||
ProgressUnit.Other
|
||||
ProgressUnit.Unspecified
|
||||
)
|
||||
progressTracker ! ProgressNotification.TaskUpdate(
|
||||
uuid,
|
||||
@ -117,4 +73,10 @@ class ControllerInterface(
|
||||
progressTracker ! ProgressNotification.TaskSuccess(uuid)
|
||||
}
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
override def sendProgressNotification(
|
||||
notification: ProgressNotification
|
||||
): Unit =
|
||||
progressTracker ! notification
|
||||
}
|
||||
|
@ -1,68 +0,0 @@
|
||||
package org.enso.projectmanager.service.versionmanagement
|
||||
|
||||
import java.util.UUID
|
||||
|
||||
import org.enso.jsonrpc.Notification
|
||||
import org.enso.projectmanager.data.ProgressUnit
|
||||
import org.enso.projectmanager.protocol.ProjectManagementApi
|
||||
|
||||
/** Internal representation of progress notifications that are sent by the
|
||||
* [[ControllerInterface]].
|
||||
*
|
||||
* They are translated by the [[RequestHandler]] into protocol progress
|
||||
* notifications.
|
||||
*/
|
||||
sealed trait ProgressNotification
|
||||
object ProgressNotification {
|
||||
|
||||
/** Singals that a new task with progress has been started. */
|
||||
case class TaskStarted(
|
||||
taskId: UUID,
|
||||
total: Option[Long],
|
||||
unit: ProgressUnit
|
||||
) extends ProgressNotification
|
||||
|
||||
/** Singals an update to task's progress. */
|
||||
case class TaskUpdate(taskId: UUID, message: Option[String], done: Long)
|
||||
extends ProgressNotification
|
||||
|
||||
/** Singals that a task has been finished successfully. */
|
||||
case class TaskSuccess(taskId: UUID) extends ProgressNotification
|
||||
|
||||
/** Singals that a task has failed. */
|
||||
case class TaskFailure(taskId: UUID, throwable: Throwable)
|
||||
extends ProgressNotification
|
||||
|
||||
/** Translates a [[ProgressNotification]] into a protocol message. */
|
||||
def translateProgressNotification(
|
||||
relatedOperationName: String,
|
||||
progressNotification: ProgressNotification
|
||||
): Notification[_, _] = progressNotification match {
|
||||
case TaskStarted(taskId, total, unit) =>
|
||||
Notification(
|
||||
ProjectManagementApi.TaskStarted,
|
||||
ProjectManagementApi.TaskStarted.Params(
|
||||
taskId = taskId,
|
||||
relatedOperation = relatedOperationName,
|
||||
unit = unit,
|
||||
total = total
|
||||
)
|
||||
)
|
||||
case TaskUpdate(taskId, message, done) =>
|
||||
Notification(
|
||||
ProjectManagementApi.TaskProgressUpdate,
|
||||
ProjectManagementApi.TaskProgressUpdate.Params(taskId, message, done)
|
||||
)
|
||||
case TaskSuccess(taskId) =>
|
||||
Notification(
|
||||
ProjectManagementApi.TaskFinished,
|
||||
ProjectManagementApi.TaskFinished.Params(taskId, None, success = true)
|
||||
)
|
||||
case TaskFailure(taskId, throwable) =>
|
||||
Notification(
|
||||
ProjectManagementApi.TaskFinished,
|
||||
ProjectManagementApi.TaskFinished
|
||||
.Params(taskId, Some(throwable.getMessage), success = false)
|
||||
)
|
||||
}
|
||||
}
|
@ -49,9 +49,7 @@ object DefaultDistributionConfiguration
|
||||
lazy val resourceManager = new ResourceManager(lockManager)
|
||||
|
||||
/** @inheritdoc */
|
||||
lazy val editionManager = new EditionManager(
|
||||
distributionManager.paths.editionSearchPaths.toList
|
||||
)
|
||||
lazy val editionManager = EditionManager(distributionManager)
|
||||
|
||||
/** @inheritdoc */
|
||||
lazy val temporaryDirectoryManager =
|
||||
|
@ -64,9 +64,7 @@ class TestDistributionConfiguration(
|
||||
|
||||
lazy val resourceManager = new ResourceManager(lockManager)
|
||||
|
||||
lazy val editionManager: EditionManager = new EditionManager(
|
||||
distributionManager.paths.editionSearchPaths.toList
|
||||
)
|
||||
lazy val editionManager: EditionManager = EditionManager(distributionManager)
|
||||
|
||||
lazy val temporaryDirectoryManager =
|
||||
new TemporaryDirectoryManager(distributionManager, resourceManager)
|
||||
|
@ -4,7 +4,6 @@ import akka.testkit.TestActors.blackholeProps
|
||||
import io.circe.Json
|
||||
import io.circe.literal.JsonStringContext
|
||||
import nl.gn0s1s.bump.SemVer
|
||||
import org.enso.editions.SemVerEnsoVersion
|
||||
import org.enso.projectmanager.data.MissingComponentAction
|
||||
import org.enso.projectmanager.{BaseServerSpec, ProjectManagementOps}
|
||||
import org.enso.testkit.RetrySpec
|
||||
@ -51,7 +50,7 @@ abstract class ProjectOpenSpecBase
|
||||
val edition = config.edition.get
|
||||
config.copy(edition =
|
||||
Some(
|
||||
edition.copy(engineVersion = Some(SemVerEnsoVersion(brokenVersion)))
|
||||
edition.copy(engineVersion = Some(brokenVersion))
|
||||
)
|
||||
)
|
||||
})
|
||||
|
@ -44,11 +44,13 @@ trait FakeEnvironment { self: HasTestDirectory =>
|
||||
val configDir = getTestDirectory.resolve("test_config")
|
||||
val binDir = getTestDirectory.resolve("test_bin")
|
||||
val runDir = getTestDirectory.resolve("test_run")
|
||||
val homeDir = getTestDirectory.resolve("test_home")
|
||||
val env = extraOverrides
|
||||
.updated("ENSO_DATA_DIRECTORY", dataDir.toString)
|
||||
.updated("ENSO_CONFIG_DIRECTORY", configDir.toString)
|
||||
.updated("ENSO_BIN_DIRECTORY", binDir.toString)
|
||||
.updated("ENSO_RUNTIME_DIRECTORY", runDir.toString)
|
||||
.updated("ENSO_HOME", homeDir.toString)
|
||||
val fakeEnvironment = new Environment {
|
||||
override def getPathToRunningExecutable: Path = executable
|
||||
|
||||
|
@ -249,8 +249,11 @@ class Runner(
|
||||
): SemVer = versionOverride.getOrElse {
|
||||
project match {
|
||||
case Some(project) =>
|
||||
val edition = project.edition
|
||||
val version = editionManager.resolveEngineVersion(edition).get
|
||||
// TODO [RW] properly get the default edition, see #1864
|
||||
val version = project.edition
|
||||
.map(edition => editionManager.resolveEngineVersion(edition).get)
|
||||
.map(SemVerEnsoVersion)
|
||||
.getOrElse(DefaultEnsoVersion)
|
||||
version match {
|
||||
case DefaultEnsoVersion =>
|
||||
globalConfigurationManager.defaultVersion
|
||||
|
@ -0,0 +1,64 @@
|
||||
package org.enso.cli.task.notifications
|
||||
|
||||
import akka.actor.ActorRef
|
||||
import org.enso.cli.task.{
|
||||
ProgressNotification,
|
||||
ProgressNotificationForwarder,
|
||||
ProgressReporter
|
||||
}
|
||||
import org.enso.cli.task.ProgressNotification.{
|
||||
TaskFailure,
|
||||
TaskStarted,
|
||||
TaskSuccess,
|
||||
TaskUpdate
|
||||
}
|
||||
import org.enso.jsonrpc.Notification
|
||||
|
||||
object ActorProgressNotificationForwarder {
|
||||
def translateAndForward(
|
||||
relatedOperationName: String,
|
||||
recipient: ActorRef
|
||||
): ProgressReporter =
|
||||
new ProgressNotificationForwarder {
|
||||
override def sendProgressNotification(
|
||||
notification: ProgressNotification
|
||||
): Unit = {
|
||||
val translated: Notification[_, _] =
|
||||
translateProgressNotification(relatedOperationName, notification)
|
||||
recipient ! translated
|
||||
}
|
||||
}
|
||||
|
||||
/** Translates a [[ProgressNotification]] into a protocol message. */
|
||||
def translateProgressNotification(
|
||||
relatedOperationName: String,
|
||||
progressNotification: ProgressNotification
|
||||
): Notification[_, _] = progressNotification match {
|
||||
case TaskStarted(taskId, total, unit) =>
|
||||
Notification(
|
||||
TaskNotificationApi.TaskStarted,
|
||||
TaskNotificationApi.TaskStarted.Params(
|
||||
taskId = taskId,
|
||||
relatedOperation = relatedOperationName,
|
||||
unit = unit,
|
||||
total = total
|
||||
)
|
||||
)
|
||||
case TaskUpdate(taskId, message, done) =>
|
||||
Notification(
|
||||
TaskNotificationApi.TaskProgressUpdate,
|
||||
TaskNotificationApi.TaskProgressUpdate.Params(taskId, message, done)
|
||||
)
|
||||
case TaskSuccess(taskId) =>
|
||||
Notification(
|
||||
TaskNotificationApi.TaskFinished,
|
||||
TaskNotificationApi.TaskFinished.Params(taskId, None, success = true)
|
||||
)
|
||||
case TaskFailure(taskId, throwable) =>
|
||||
Notification(
|
||||
TaskNotificationApi.TaskFinished,
|
||||
TaskNotificationApi.TaskFinished
|
||||
.Params(taskId, Option(throwable.getMessage), success = false)
|
||||
)
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
package org.enso.cli.task.notifications
|
||||
|
||||
import enumeratum._
|
||||
import org.enso.cli.task.{ProgressUnit => TaskProgressUnit}
|
||||
|
||||
/** Represents the unit used by progress updates. */
|
||||
sealed trait SerializableProgressUnit extends EnumEntry
|
||||
object SerializableProgressUnit
|
||||
extends Enum[SerializableProgressUnit]
|
||||
with CirceEnum[SerializableProgressUnit] {
|
||||
|
||||
/** Indicates that progress is measured by amount of bytes processed. */
|
||||
case object Bytes extends SerializableProgressUnit
|
||||
|
||||
/** Indicates that progress is measured by some other unit or it is not
|
||||
* measured at all.
|
||||
*/
|
||||
case object Other extends SerializableProgressUnit
|
||||
|
||||
override val values = findValues
|
||||
|
||||
/** Converts a [[TaskProgressUnit]] to [[SerializableProgressUnit]].
|
||||
*/
|
||||
implicit def fromUnit(unit: TaskProgressUnit): SerializableProgressUnit =
|
||||
unit match {
|
||||
case TaskProgressUnit.Bytes => Bytes
|
||||
case TaskProgressUnit.Unspecified => Other
|
||||
}
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
package org.enso.cli.task.notifications
|
||||
|
||||
import org.enso.jsonrpc.{HasParams, Method}
|
||||
|
||||
import java.util.UUID
|
||||
|
||||
object TaskNotificationApi {
|
||||
|
||||
case object TaskStarted extends Method("task/started") {
|
||||
|
||||
case class Params(
|
||||
taskId: UUID,
|
||||
relatedOperation: String,
|
||||
unit: SerializableProgressUnit,
|
||||
total: Option[Long]
|
||||
)
|
||||
|
||||
implicit val hasParams = new HasParams[this.type] {
|
||||
type Params = TaskStarted.Params
|
||||
}
|
||||
}
|
||||
|
||||
case object TaskProgressUpdate extends Method("task/progress-update") {
|
||||
|
||||
case class Params(
|
||||
taskId: UUID,
|
||||
message: Option[String],
|
||||
done: Long
|
||||
)
|
||||
|
||||
implicit val hasParams = new HasParams[this.type] {
|
||||
type Params = TaskProgressUpdate.Params
|
||||
}
|
||||
}
|
||||
|
||||
case object TaskFinished extends Method("task/finished") {
|
||||
|
||||
case class Params(
|
||||
taskId: UUID,
|
||||
message: Option[String],
|
||||
success: Boolean
|
||||
)
|
||||
|
||||
implicit val hasParams = new HasParams[this.type] {
|
||||
type Params = TaskFinished.Params
|
||||
}
|
||||
}
|
||||
}
|
@ -1,3 +1,3 @@
|
||||
E4A61C4649AD6FB148D4779D9B20B395AEAB73475EE13D20EFB55C163724A77B
|
||||
C03EC922F039EB5CC96F93A489453ADDD4FA6EBBF75713B05FE67B48CFF6ACBF
|
||||
64FE86A276F737CE2B4D352D8F35F790CA0DA18F329A48A467F34FC9C3CAF07D
|
||||
0
|
||||
|
@ -1,3 +1,3 @@
|
||||
0BA0D3694722E724BABC3D4D860FA5D11DA541B20632F18175E1F45BF44DE717
|
||||
0AED341E22D16C5722BF722FD5F92039464E9FE3CCD3288D3643F05E09AF62FB
|
||||
B7948AB0E996317E7F073260BA28A63D14215436079C09E793F803CF999D607C
|
||||
0
|
||||
|
Loading…
Reference in New Issue
Block a user