diff --git a/RELEASES.md b/RELEASES.md index 0edb9cd641d8..44ee47f15519 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -10,6 +10,9 @@ - Implemented an HTTP endponint returning the time that the language server has spent idle ([#1847](https://github.com/enso-org/enso/pull/1847)). +- Fix a bug where the `project/list` endpoint would fail if any of the projects + referenced an edition that does not exist anymore + ([#1858](https://github.com/enso-org/enso/pull/1858)). # Enso 0.2.13 (2021-07-09) diff --git a/docs/language-server/protocol-project-manager.md b/docs/language-server/protocol-project-manager.md index 222d60b22226..217ec9482e25 100644 --- a/docs/language-server/protocol-project-manager.md +++ b/docs/language-server/protocol-project-manager.md @@ -101,8 +101,11 @@ interface ProjectMetadata { /** * Enso Engine version to use for the project, represented by a semver version * string. + * + * If the edition associated with the project could not be resolved, the + * engine version may be missing. */ - engineVersion: String; + engineVersion?: String; /** * The last opened datetime. diff --git a/lib/scala/project-manager/src/main/scala/org/enso/projectmanager/data/ProjectMetadata.scala b/lib/scala/project-manager/src/main/scala/org/enso/projectmanager/data/ProjectMetadata.scala index 28e390dd7ccc..8dc92581559b 100644 --- a/lib/scala/project-manager/src/main/scala/org/enso/projectmanager/data/ProjectMetadata.scala +++ b/lib/scala/project-manager/src/main/scala/org/enso/projectmanager/data/ProjectMetadata.scala @@ -10,13 +10,14 @@ import nl.gn0s1s.bump.SemVer * @param name the name of the project * @param namespace the namespace of the project * @param id the project id - * @param engineVersion version of the engine associated with the project + * @param engineVersion version of the engine associated with the project, it + * may be missing if project's edition could not be loaded * @param lastOpened the last opened datetime */ case class ProjectMetadata( name: String, namespace: String, id: UUID, - engineVersion: SemVer, + engineVersion: Option[SemVer], lastOpened: Option[OffsetDateTime] ) diff --git a/lib/scala/project-manager/src/main/scala/org/enso/projectmanager/service/ProjectService.scala b/lib/scala/project-manager/src/main/scala/org/enso/projectmanager/service/ProjectService.scala index 71abf19fcdf1..9918b21e28bb 100644 --- a/lib/scala/project-manager/src/main/scala/org/enso/projectmanager/service/ProjectService.scala +++ b/lib/scala/project-manager/src/main/scala/org/enso/projectmanager/service/ProjectService.scala @@ -2,6 +2,7 @@ package org.enso.projectmanager.service 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.pkg.Config @@ -75,6 +76,8 @@ class ProjectService[ )(implicit E: MonadError[F[ProjectServiceFailure, *], ProjectServiceFailure]) extends ProjectServiceApi[F] { + private lazy val logger = Logger[ProjectService[F]] + import E._ /** @inheritdoc */ @@ -367,8 +370,8 @@ class ProjectService[ private def resolveProjectMetadata( project: Project - ): F[ProjectServiceFailure, ProjectMetadata] = - for { + ): F[ProjectServiceFailure, ProjectMetadata] = { + val version = for { version <- resolveProjectVersion(project) version <- configurationService .resolveEnsoVersion(version) @@ -378,10 +381,23 @@ class ProjectService[ message ) } + } yield version + + for { + version <- version.map(Some(_)).recover { error => + // TODO [RW] We may consider sending this warning to the IDE once + // a warning protocol is implemented (#1860). + logger.warn( + s"Could not resolve engine version for project ${project.name}: " + + s"$error" + ) + None + } } yield toProjectMetadata(version, project) + } private def toProjectMetadata( - engineVersion: SemVer, + engineVersion: Option[SemVer], project: Project ): ProjectMetadata = ProjectMetadata( diff --git a/lib/scala/project-manager/src/test/scala/org/enso/projectmanager/BaseServerSpec.scala b/lib/scala/project-manager/src/test/scala/org/enso/projectmanager/BaseServerSpec.scala index 325da2cd50fc..4dd99519e59a 100644 --- a/lib/scala/project-manager/src/test/scala/org/enso/projectmanager/BaseServerSpec.scala +++ b/lib/scala/project-manager/src/test/scala/org/enso/projectmanager/BaseServerSpec.scala @@ -12,10 +12,12 @@ import nl.gn0s1s.bump.SemVer import org.apache.commons.io.FileUtils import org.enso.distribution.FileSystem.PathSyntax import org.enso.distribution.{FileSystem, OS} +import org.enso.editions.Editions import org.enso.jsonrpc.test.JsonRpcServerTestKit import org.enso.jsonrpc.{ClientControllerFactory, Protocol} import org.enso.loggingservice.printers.StderrPrinterWithColors import org.enso.loggingservice.{LogLevel, LoggerMode, LoggingServiceManager} +import org.enso.pkg.{Config, PackageManager} import org.enso.projectmanager.boot.Globals.{ConfigFilename, ConfigNamespace} import org.enso.projectmanager.boot.configuration._ import org.enso.projectmanager.control.effect.ZioEnvExec @@ -350,4 +352,22 @@ class BaseServerSpec extends JsonRpcServerTestKit with BeforeAndAfterAll { ) shouldEqual json } + /** Modifies the project's package.yaml. */ + def updateProjectConfig( + projectName: String + )(update: Config => Config): Unit = { + val pkgFile = new File(userProjectDir, projectName) + val pkg = PackageManager.Default.loadPackage(pkgFile).get + pkg.updateConfig(update) + } + + /** Sets project's parent edition. */ + def setProjectParentEdition( + projectName: String, + newParentEditionName: String + ): Unit = updateProjectConfig(projectName) { config => + config.copy(edition = + Some(Editions.Raw.Edition(parent = Some(newParentEditionName))) + ) + } } diff --git a/lib/scala/project-manager/src/test/scala/org/enso/projectmanager/protocol/ProjectManagementApiSpec.scala b/lib/scala/project-manager/src/test/scala/org/enso/projectmanager/protocol/ProjectManagementApiSpec.scala index 5523eeda89b4..46acac546081 100644 --- a/lib/scala/project-manager/src/test/scala/org/enso/projectmanager/protocol/ProjectManagementApiSpec.scala +++ b/lib/scala/project-manager/src/test/scala/org/enso/projectmanager/protocol/ProjectManagementApiSpec.scala @@ -1,9 +1,5 @@ package org.enso.projectmanager.protocol -import java.io.File -import java.nio.file.Paths -import java.util.UUID - import io.circe.literal._ import nl.gn0s1s.bump.SemVer import org.apache.commons.io.FileUtils @@ -11,6 +7,9 @@ import org.enso.editions.SemVerJson._ import org.enso.projectmanager.{BaseServerSpec, ProjectManagementOps} import org.enso.testkit.{FlakySpec, RetrySpec} +import java.io.File +import java.nio.file.Paths +import java.util.UUID import scala.io.Source class ProjectManagementApiSpec @@ -346,6 +345,37 @@ class ProjectManagementApiSpec """) } + "fail when project's edition could not be resolved" in { + implicit val client = new WsTestClient(address) + val projectId = createProject("Foo") + setProjectParentEdition( + "Foo", + "some_weird_edition_name_that-surely-does-not-exist" + ) + + client.send(json""" + { "jsonrpc": "2.0", + "method": "project/open", + "id": 0, + "params": { + "projectId": $projectId + } + } + """) + client.expectJson(json""" + { + "jsonrpc":"2.0", + "id":0, + "error":{ + "code" : 4011, + "message" : "Could not resolve project engine version: Cannot load the edition: Could not find edition `some_weird_edition_name_that-surely-does-not-exist`." + } + } + """) + + deleteProject(projectId) + } + "start the Language Server if not running" taggedAs Flaky in { //given val projectName = "To_Remove" @@ -633,6 +663,54 @@ class ProjectManagementApiSpec deleteProject(bazId) } + "return a list of projects even if editions of some of them cannot be resolved" in { + implicit val client = new WsTestClient(address) + //given + val fooId = createProject("Foo") + testClock.moveTimeForward() + val barId = createProject("Bar") + setProjectParentEdition( + "Bar", + "some_weird_edition_name_that-surely-does-not-exist" + ) + + //when + client.send(json""" + { "jsonrpc": "2.0", + "method": "project/list", + "id": 0, + "params": { } + } + """) + //then + client.expectJson(json""" + { + "jsonrpc":"2.0", + "id":0, + "result": { + "projects": [ + { + "name": "Bar", + "namespace": "local", + "id": $barId, + "engineVersion": null, + "lastOpened": null + }, + { + "name": "Foo", + "namespace": "local", + "id": $fooId, + "engineVersion": $engineToInstall, + "lastOpened": null + } + ] + } + } + """) + deleteProject(fooId) + deleteProject(barId) + } + "returned sorted list of recently opened projects" in { implicit val client = new WsTestClient(address) //given