New Language Server API Implementations / Mocks (#1875)

This commit is contained in:
Radosław Waśko 2021-07-17 16:49:51 +02:00 committed by GitHub
parent 4235d345aa
commit 86fcd86055
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
79 changed files with 2911 additions and 466 deletions

View File

@ -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

View File

@ -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(

View File

@ -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;
}
```

View File

@ -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 [{}].",

View File

@ -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
)
)
}
}
}

View File

@ -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))
}

View File

@ -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()
}
}

View File

@ -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}
} """
)
}
}

View File

@ -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
)
)
}
}
}

View File

@ -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)
)
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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)
)
}

View File

@ -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)
)
}

View File

@ -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)
)
}

View File

@ -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)
)
}

View File

@ -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)
)
}

View File

@ -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
)
)
}

View File

@ -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)
)
}

View File

@ -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)
}

View File

@ -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)
)
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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
)
)

View File

@ -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
)
)
}

View File

@ -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)

View File

@ -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>"

View File

@ -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)
}
}
}

View File

@ -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)
}
}
}

View File

@ -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
}
}

View File

@ -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
}
}
""")
}
}
}

View File

@ -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 {}
}
}

View File

@ -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)

View File

@ -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(

View File

@ -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 =

View File

@ -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

View File

@ -338,10 +338,7 @@ object PackageRepository {
.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 editionManager = EditionManager(distributionManager, homeManager)
val edition = editionManager.resolveEdition(rawEdition).get
val resolvingLibraryProvider =

View File

@ -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}
@ -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
)
)
}
}

View File

@ -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)
}
}

View File

@ -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
}

View File

@ -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))
}
})
}
}

View File

@ -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
}

View File

@ -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)
)
}

View File

@ -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)
}
}

View File

@ -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` " +

View File

@ -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
)
)

View File

@ -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)

View File

@ -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. */

View File

@ -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

View File

@ -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.

View File

@ -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()
}

View File

@ -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()
)

View File

@ -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
)

View File

@ -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()
}
}

View File

@ -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) =>

View File

@ -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 " +

View File

@ -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
}

View File

@ -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))

View File

@ -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()
)

View File

@ -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 = {
def save(): Try[Unit] = for {
_ <- Try {
if (!root.exists) createDirectories()
if (!sourceDir.exists) createSourceDir()
saveConfig()
}
_ <- saveConfig()
} yield ()
/** Creates the package directory structure.
*/
@ -97,10 +99,9 @@ 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,

View File

@ -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

View File

@ -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
}
}

View File

@ -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.
*

View File

@ -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])

View File

@ -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.

View File

@ -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,14 +305,6 @@ 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)
sockets <- languageServerGateway
.start(progressTracker, clientId, project, version)
@ -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 =>

View File

@ -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
}

View File

@ -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)
)
}
}

View File

@ -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 =

View File

@ -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)

View File

@ -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))
)
)
})

View File

@ -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

View File

@ -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

View File

@ -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)
)
}
}

View File

@ -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
}
}

View File

@ -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
}
}
}

View File

@ -1,3 +1,3 @@
E4A61C4649AD6FB148D4779D9B20B395AEAB73475EE13D20EFB55C163724A77B
C03EC922F039EB5CC96F93A489453ADDD4FA6EBBF75713B05FE67B48CFF6ACBF
64FE86A276F737CE2B4D352D8F35F790CA0DA18F329A48A467F34FC9C3CAF07D
0

View File

@ -1,3 +1,3 @@
0BA0D3694722E724BABC3D4D860FA5D11DA541B20632F18175E1F45BF44DE717
0AED341E22D16C5722BF722FD5F92039464E9FE3CCD3288D3643F05E09AF62FB
B7948AB0E996317E7F073260BA28A63D14215436079C09E793F803CF999D607C
0