Update the editions/listDefinedLibraries Endpoint (#1964)

This commit is contained in:
Radosław Waśko 2021-08-19 17:21:31 +02:00 committed by GitHub
parent f53744ff53
commit 63819526d7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 525 additions and 211 deletions

View File

@ -1330,6 +1330,7 @@ interface LibraryEntry {
namespace: String;
name: String;
version: LibraryVersion;
isCached: Boolean;
}
```

View File

@ -14,6 +14,7 @@ import org.enso.languageserver.http.server.BinaryWebSocketServer
import org.enso.languageserver.io._
import org.enso.languageserver.libraries.{
EditionReferenceResolver,
LibraryConfig,
LocalLibraryManager,
ProjectSettingsManager
}
@ -36,6 +37,9 @@ import org.enso.languageserver.search.SuggestionsHandler
import org.enso.languageserver.session.SessionRouter
import org.enso.languageserver.text.BufferRegistry
import org.enso.languageserver.util.binary.BinaryEncoder
import org.enso.librarymanager.LibraryLocations
import org.enso.librarymanager.local.DefaultLocalLibraryProvider
import org.enso.librarymanager.published.PublishedLibraryCache
import org.enso.loggingservice.{JavaLoggingLogHandler, LogLevel}
import org.enso.polyglot.{RuntimeOptions, RuntimeServerInfo}
import org.enso.searcher.sql.{SqlDatabase, SqlSuggestionsRepo, SqlVersionsRepo}
@ -292,24 +296,34 @@ class MainModule(serverConfig: LanguageServerConfig, logLevel: LogLevel) {
"local-library-manager"
)
val jsonRpcControllerFactory = new JsonConnectionControllerFactory(
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,
val libraryLocations =
LibraryLocations.resolve(distributionManager, Some(languageHome))
val libraryConfig = LibraryConfig(
localLibraryManager = localLibraryManager,
editionReferenceResolver = editionReferenceResolver,
editionManager = editionManager,
config = languageServerConfig
localLibraryProvider = DefaultLocalLibraryProvider.make(libraryLocations),
publishedLibraryCache =
PublishedLibraryCache.makeReadOnlyCache(libraryLocations)
)
val jsonRpcControllerFactory = new JsonConnectionControllerFactory(
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,
libraryConfig = libraryConfig,
config = languageServerConfig
)
log.trace(
"Created JSON connection controller factory [{}].",

View File

@ -0,0 +1,22 @@
package org.enso.languageserver.libraries
import akka.actor.ActorRef
import org.enso.editions.updater.EditionManager
import org.enso.librarymanager.local.LocalLibraryProvider
import org.enso.librarymanager.published.PublishedLibraryCache
/** Gathers together components needed by library-related Language Server endpoints.
*
* @param localLibraryManager a reference to the local library manager actor
* @param editionReferenceResolver an instance of edition reference resolver
* @param editionManager an instance of edition manager
* @param localLibraryProvider an instance of local library provider
* @param publishedLibraryCache an instance of published library cache
*/
case class LibraryConfig(
localLibraryManager: ActorRef,
editionReferenceResolver: EditionReferenceResolver,
editionManager: EditionManager,
localLibraryProvider: LocalLibraryProvider,
publishedLibraryCache: PublishedLibraryCache
)

View File

@ -10,11 +10,14 @@ import org.enso.editions
* @param namespace namespace of the library
* @param name name of the library
* @param version version of the library
* @param isCached indicates whether the library is available in one of local
* caches
*/
case class LibraryEntry(
namespace: String,
name: String,
version: LibraryEntry.LibraryVersion
version: LibraryEntry.LibraryVersion,
isCached: Boolean
)
object LibraryEntry {

View File

@ -101,7 +101,12 @@ class LocalLibraryManager(
private def listLocalLibraries(): Try[ListLocalLibrariesResponse] = for {
libraryNames <- findLocalLibraries()
libraryEntries = libraryNames.distinct.map { name =>
LibraryEntry(name.namespace, name.name, LibraryEntry.LocalLibraryVersion)
LibraryEntry(
name.namespace,
name.name,
LibraryEntry.LocalLibraryVersion,
isCached = true
)
}
} yield ListLocalLibrariesResponse(libraryEntries)

View File

@ -2,23 +2,30 @@ package org.enso.languageserver.libraries.handler
import akka.actor.{Actor, Props}
import com.typesafe.scalalogging.LazyLogging
import org.enso.editions.LibraryVersion
import org.enso.jsonrpc.{Request, ResponseError, ResponseResult}
import org.enso.languageserver.filemanager.FileManagerApi.FileSystemError
import org.enso.languageserver.libraries.LibraryApi._
import org.enso.languageserver.libraries.{
EditionReferenceResolver,
LibraryEntry
}
import org.enso.languageserver.libraries.LibraryApi._
import org.enso.languageserver.util.UnhandledLogging
import org.enso.librarymanager.local.LocalLibraryProvider
import org.enso.librarymanager.published.PublishedLibraryCache
import scala.util.{Failure, Success}
/** A request handler for the `editions/listDefinedLibraries` endpoint.
*
* @param editionReferenceResolver an [[EditionReferenceResolver]] instance
* @param localLibraryProvider a provider of local libraries
* @param publishedLibraryCache a cache of published libraries
*/
class EditionsListDefinedLibrariesHandler(
editionReferenceResolver: EditionReferenceResolver
editionReferenceResolver: EditionReferenceResolver,
localLibraryProvider: LocalLibraryProvider,
publishedLibraryCache: PublishedLibraryCache
) extends Actor
with LazyLogging
with UnhandledLogging {
@ -31,10 +38,17 @@ class EditionsListDefinedLibrariesHandler(
val result = for {
edition <- editionReferenceResolver.resolveEdition(reference)
} yield edition.getAllDefinedLibraries.toSeq.map { case (name, version) =>
val isCached = version match {
case LibraryVersion.Local =>
localLibraryProvider.findLibrary(name).isDefined
case LibraryVersion.Published(version, _) =>
publishedLibraryCache.isLibraryCached(name, version)
}
LibraryEntry(
namespace = name.namespace,
name = name.name,
version = version
version = version,
isCached = isCached
)
}
@ -62,8 +76,18 @@ object EditionsListDefinedLibrariesHandler {
* [[EditionsListDefinedLibrariesHandler]].
*
* @param editionReferenceResolver an [[EditionReferenceResolver]] instance
* @param localLibraryProvider a provider of local libraries
* @param publishedLibraryCache a cache of published libraries
*/
def props(editionReferenceResolver: EditionReferenceResolver): Props = Props(
new EditionsListDefinedLibrariesHandler(editionReferenceResolver)
def props(
editionReferenceResolver: EditionReferenceResolver,
localLibraryProvider: LocalLibraryProvider,
publishedLibraryCache: PublishedLibraryCache
): Props = Props(
new EditionsListDefinedLibrariesHandler(
editionReferenceResolver,
localLibraryProvider,
publishedLibraryCache
)
)
}

View File

@ -6,7 +6,6 @@ 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.editions.updater.EditionManager
import org.enso.jsonrpc._
import org.enso.languageserver.boot.resource.InitializationComponent
import org.enso.languageserver.capability.CapabilityApi.{
@ -27,8 +26,8 @@ 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.LibraryConfig
import org.enso.languageserver.libraries.handler._
import org.enso.languageserver.monitoring.MonitoringApi.{InitialPing, Ping}
import org.enso.languageserver.monitoring.MonitoringProtocol
@ -89,6 +88,7 @@ import scala.concurrent.duration._
* @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 libraryConfig configuration of the library ecosystem
* @param requestTimeout a request timeout
*/
class JsonConnectionController(
@ -106,9 +106,7 @@ class JsonConnectionController(
val runtimeConnector: ActorRef,
val idlenessMonitor: ActorRef,
val projectSettingsManager: ActorRef,
val localLibraryManager: ActorRef,
val editionReferenceResolver: EditionReferenceResolver,
val editionManager: EditionManager,
val libraryConfig: LibraryConfig,
val languageServerConfig: Config,
requestTimeout: FiniteDuration = 10.seconds
) extends Actor
@ -495,24 +493,28 @@ class JsonConnectionController(
EditionsGetProjectSettings -> EditionsGetProjectSettingsHandler
.props(requestTimeout, projectSettingsManager),
EditionsListAvailable -> EditionsListAvailableHandler.props(
editionManager
libraryConfig.editionManager
),
EditionsListDefinedLibraries -> EditionsListDefinedLibrariesHandler
.props(editionReferenceResolver),
.props(
libraryConfig.editionReferenceResolver,
libraryConfig.localLibraryProvider,
libraryConfig.publishedLibraryCache
),
EditionsResolve -> EditionsResolveHandler
.props(editionReferenceResolver),
.props(libraryConfig.editionReferenceResolver),
EditionsSetParentEdition -> EditionsSetParentEditionHandler
.props(requestTimeout, projectSettingsManager),
EditionsSetLocalLibrariesPreference -> EditionsSetProjectLocalLibrariesPreferenceHandler
.props(requestTimeout, projectSettingsManager),
LibraryCreate -> LibraryCreateHandler
.props(requestTimeout, localLibraryManager),
.props(requestTimeout, libraryConfig.localLibraryManager),
LibraryListLocal -> LibraryListLocalHandler
.props(requestTimeout, localLibraryManager),
.props(requestTimeout, libraryConfig.localLibraryManager),
LibraryGetMetadata -> LibraryGetMetadataHandler.props(),
LibraryPreinstall -> LibraryPreinstallHandler.props(),
LibraryPublish -> LibraryPublishHandler
.props(requestTimeout, localLibraryManager),
.props(requestTimeout, libraryConfig.localLibraryManager),
LibrarySetMetadata -> LibrarySetMetadataHandler.props()
)
}
@ -557,6 +559,7 @@ object JsonConnectionController {
* @param contentRootManager manages the available content roots
* @param contextRegistry a router that dispatches execution context requests
* @param suggestionsHandler a reference to the suggestions requests handler
* @param libraryConfig configuration of the library ecosystem
* @param requestTimeout a request timeout
* @return a configuration object
*/
@ -575,33 +578,29 @@ object JsonConnectionController {
runtimeConnector: ActorRef,
idlenessMonitor: ActorRef,
projectSettingsManager: ActorRef,
localLibraryManager: ActorRef,
editionReferenceResolver: EditionReferenceResolver,
editionManager: EditionManager,
libraryConfig: LibraryConfig,
languageServerConfig: Config,
requestTimeout: FiniteDuration = 10.seconds
): Props =
Props(
new JsonConnectionController(
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
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,
libraryConfig = libraryConfig,
languageServerConfig = languageServerConfig,
requestTimeout = requestTimeout
)
)

View File

@ -1,11 +1,10 @@
package org.enso.languageserver.protocol.json
import akka.actor.{ActorRef, ActorSystem}
import org.enso.editions.updater.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 org.enso.languageserver.libraries.LibraryConfig
import java.util.UUID
@ -15,6 +14,7 @@ import java.util.UUID
* @param bufferRegistry the buffer registry actor ref
* @param capabilityRouter the capability router actor ref
* @param system the actor system
* @param libraryConfig configuration of the library ecosystem
*/
class JsonConnectionControllerFactory(
mainComponent: InitializationComponent,
@ -30,9 +30,7 @@ class JsonConnectionControllerFactory(
runtimeConnector: ActorRef,
idlenessMonitor: ActorRef,
projectSettingsManager: ActorRef,
localLibraryManager: ActorRef,
editionReferenceResolver: EditionReferenceResolver,
editionManager: EditionManager,
libraryConfig: LibraryConfig,
config: Config
)(implicit system: ActorSystem)
extends ClientControllerFactory {
@ -45,24 +43,22 @@ class JsonConnectionControllerFactory(
override def createClientController(clientId: UUID): ActorRef =
system.actorOf(
JsonConnectionController.props(
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
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,
libraryConfig = libraryConfig,
languageServerConfig = config
)
)
}

View File

@ -7,13 +7,20 @@ 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)
val entry1 =
LibraryEntry(
"Foo",
"Bar",
LibraryEntry.LocalLibraryVersion,
isCached = true
)
entry1.asJson.as[LibraryEntry] shouldEqual Right(entry1)
val entry2 = LibraryEntry(
"Foo",
"Bar",
LibraryEntry.PublishedLibraryVersion("1.2.3", "https://example.com/")
LibraryEntry.PublishedLibraryVersion("1.2.3", "https://example.com/"),
isCached = false
)
entry2.asJson.as[LibraryEntry] shouldEqual Right(entry2)
}

View File

@ -6,7 +6,7 @@ import io.circe.parser.parse
import io.circe.syntax.EncoderOps
import org.apache.commons.io.FileUtils
import org.enso.distribution.{DistributionManager, LanguageHome}
import org.enso.editions.EditionResolver
import org.enso.editions.{EditionResolver, Editions}
import org.enso.editions.updater.EditionManager
import org.enso.jsonrpc.test.JsonRpcServerTestKit
import org.enso.jsonrpc.{ClientControllerFactory, Protocol}
@ -24,6 +24,7 @@ import org.enso.languageserver.filemanager._
import org.enso.languageserver.io._
import org.enso.languageserver.libraries.{
EditionReferenceResolver,
LibraryConfig,
LocalLibraryManager,
ProjectSettingsManager
}
@ -37,6 +38,9 @@ import org.enso.languageserver.runtime.{ContextRegistry, RuntimeFailureMapper}
import org.enso.languageserver.search.SuggestionsHandler
import org.enso.languageserver.session.SessionRouter
import org.enso.languageserver.text.BufferRegistry
import org.enso.librarymanager.LibraryLocations
import org.enso.librarymanager.local.DefaultLocalLibraryProvider
import org.enso.librarymanager.published.PublishedLibraryCache
import org.enso.pkg.PackageManager
import org.enso.polyglot.data.TypeGraph
import org.enso.polyglot.runtime.Runtime.Api
@ -47,7 +51,7 @@ import org.enso.text.Sha3_224VersionCalculator
import org.scalatest.OptionValues
import java.nio.file
import java.nio.file.Files
import java.nio.file.{Files, Path}
import java.util.UUID
import scala.concurrent.Await
import scala.concurrent.duration._
@ -221,21 +225,6 @@ 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)
@ -267,35 +256,59 @@ class BaseServerTest
)
)
new JsonConnectionControllerFactory(
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,
val libraryLocations =
LibraryLocations.resolve(distributionManager, Some(languageHome))
val libraryConfig = LibraryConfig(
localLibraryManager = localLibraryManager,
editionReferenceResolver = editionReferenceResolver,
editionManager = editionManager,
config = config
localLibraryProvider = DefaultLocalLibraryProvider.make(libraryLocations),
publishedLibraryCache =
PublishedLibraryCache.makeReadOnlyCache(libraryLocations)
)
new JsonConnectionControllerFactory(
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,
libraryConfig = libraryConfig,
config = config
)
}
/** As we are testing the language server, we want to imitate the location
* context of the runner.jar, while the default implementation of this method
* was more suited towards testing the launcher.
*/
override def fakeExecutablePath(portable: Boolean): Path =
Path.of("distribution/component/runner.jar")
/** Specifies if the `package.yaml` at project root should be auto-created. */
protected def initializeProjectPackage: Boolean = true
/** Allows to customize the edition used by the project.
*
* Only applicable if [[initializeProjectPackage]] is [[true]].
*/
protected def customEdition: Option[Editions.RawEdition] = None
lazy val initPackage: Unit = {
if (initializeProjectPackage) {
PackageManager.Default.create(
config.projectContentRoot.file,
name = "TestProject"
name = "TestProject",
edition = customEdition
)
}
}

View File

@ -2,6 +2,8 @@ package org.enso.languageserver.websocket.json
import io.circe.literal._
import io.circe.{Json, JsonObject}
import nl.gn0s1s.bump.SemVer
import org.enso.editions.{Editions, LibraryName}
import org.enso.languageserver.libraries.LibraryEntry
import org.enso.languageserver.libraries.LibraryEntry.PublishedLibraryVersion
import org.enso.librarymanager.published.repository.{
@ -12,6 +14,20 @@ import org.enso.librarymanager.published.repository.{
import java.nio.file.Files
class LibrariesTest extends BaseServerTest {
override protected def customEdition: Option[Editions.RawEdition] = Some(
Editions.Raw.Edition
.make(
parent = Some(buildinfo.Info.currentEdition),
libraries = Seq(
Editions.Raw.PublishedLibrary(
name = LibraryName("Foo", "Bar"),
version = SemVer(1, 2, 3),
repository = "main"
)
)
)
)
"LocalLibraryManager" should {
"create a library project and include it on the list of local projects" in {
val client = getInitialisedWsClient()
@ -66,7 +82,8 @@ class LibrariesTest extends BaseServerTest {
"name": "My_Local_Lib",
"version": {
"type": "LocalLibraryVersion"
}
},
"isCached": true
}
]
}
@ -254,21 +271,31 @@ class LibrariesTest extends BaseServerTest {
}
}
"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
}
case class PublishedLibrary(
namespace: String,
name: String,
isCached: Boolean
)
def extractPublishedLibraries(response: Json): Seq[PublishedLibrary] = {
val result = response.asObject.value("result").value
val libs = result.asObject.value("availableLibraries").value
val parsed = libs.asArray.value.map(_.as[LibraryEntry])
parsed.collect {
case Right(
LibraryEntry(
namespace,
name,
PublishedLibraryVersion(_, _),
isCached
)
) =>
PublishedLibrary(namespace, name, isCached)
}
}
"editions/listDefinedLibraries" should {
"include expected libraries in the list" in {
val client = getInitialisedWsClient()
client.send(json"""
{ "jsonrpc": "2.0",
@ -281,7 +308,14 @@ class LibrariesTest extends BaseServerTest {
}
}
""")
containsBase(client.expectSomeJson())
val published = extractPublishedLibraries(client.expectSomeJson())
published should contain(
PublishedLibrary("Standard", "Base", isCached = true)
)
published should contain(
PublishedLibrary("Foo", "Bar", isCached = false)
)
val currentEditionName = buildinfo.Info.currentEdition
client.send(json"""
@ -296,7 +330,9 @@ class LibrariesTest extends BaseServerTest {
}
}
""")
containsBase(client.expectSomeJson())
extractPublishedLibraries(client.expectSomeJson()) should contain(
PublishedLibrary("Standard", "Base", isCached = true)
)
}
}

View File

@ -344,7 +344,7 @@ object PackageRepository {
val edition = editionManager.resolveEdition(rawEdition).get
val resolvingLibraryProvider =
new DefaultLibraryProvider(
DefaultLibraryProvider.make(
distributionManager = distributionManager,
resourceManager = resourceManager,
lockUserInterface = notificationHandler,

View File

@ -85,6 +85,34 @@ trait Editions {
"that will imply it."
)
}
object Edition {
/** Alternative constructor for creating editions.
*
* Useful for manually created editions.
*
* @param parent a parent edition (if applicable)
* @param engineVersion an engine version; it should be defined if the
* edition wants to override the setting from the parent
* or if it has no parents
* @param repositories a list of repositories directly defined in the
* edition (does not include ones defined in the parents)
* @param libraries a list of libraries directly defined in the edition
* (does not include ones defined in the parents)
*/
def make(
parent: Option[NestedEditionType] = None,
engineVersion: Option[SemVer] = None,
repositories: Seq[Editions.Repository] = Seq.empty,
libraries: Seq[Library] = Seq.empty
): Edition = Edition(
parent,
engineVersion,
repositories.map(r => (r.name, r)).toMap,
libraries.map(l => (l.name, l)).toMap
)
}
}
object Editions {

View File

@ -9,82 +9,34 @@ import org.enso.distribution.{
TemporaryDirectoryManager
}
import org.enso.editions.{Editions, LibraryName, LibraryVersion}
import org.enso.librarymanager.local.DefaultLocalLibraryProvider
import org.enso.librarymanager.local.{
DefaultLocalLibraryProvider,
LocalLibraryProvider
}
import org.enso.librarymanager.published.bundles.LocalReadOnlyRepository
import org.enso.librarymanager.published.cache.DownloadingLibraryCache
import org.enso.librarymanager.published.{
DefaultPublishedLibraryProvider,
PublishedLibraryProvider
}
import org.enso.logger.masking.MaskedPath
import java.nio.file.Path
/** A helper class for loading libraries.
*
* @param distributionManager a distribution manager
* @param resourceManager a resource manager
* @param lockUserInterface an interface that will handle notifications
* about waiting on locks
* @param progressReporter an interface that will handle progress
* notifications
* @param languageHome a language home which may contain bundled libraries
* @param edition the edition used in the project
* @param preferLocalLibraries project setting whether to use local libraries
* @param localLibraryProvider provider of local (unpublished) libraries
* @param publishedLibraryProvider provider of published libraries
* @param edition the edition used in the project
* @param preferLocalLibraries project setting whether to use local
* libraries
*/
class DefaultLibraryProvider(
distributionManager: DistributionManager,
resourceManager: ResourceManager,
lockUserInterface: LockUserInterface,
progressReporter: ProgressReporter,
languageHome: Option[LanguageHome],
class DefaultLibraryProvider private (
localLibraryProvider: LocalLibraryProvider,
publishedLibraryProvider: PublishedLibraryProvider,
edition: Editions.ResolvedEdition,
preferLocalLibraries: Boolean
) extends ResolvingLibraryProvider {
private val logger = Logger[DefaultLibraryProvider]
private val localLibrarySearchPaths =
distributionManager.paths.localLibrariesSearchPaths.toList
private val localLibraryProvider = new DefaultLocalLibraryProvider(
localLibrarySearchPaths
)
private val logger = Logger[DefaultLibraryProvider]
private val resolver = LibraryResolver(localLibraryProvider)
private val cacheRoot = distributionManager.paths.cachedLibraries
private val primaryCache = new DownloadingLibraryCache(
cacheRoot,
TemporaryDirectoryManager(distributionManager, resourceManager),
resourceManager,
lockUserInterface,
progressReporter
)
private val additionalCacheLocations = {
val engineBundleRoot = languageHome.map(_.libraries)
val locations =
engineBundleRoot.toList ++ distributionManager.auxiliaryLibraryCaches()
locations.distinct
}
private val additionalCaches =
additionalCacheLocations.map(new LocalReadOnlyRepository(_))
private val publishedLibraryProvider: PublishedLibraryProvider =
new DefaultPublishedLibraryProvider(primaryCache, additionalCaches)
locally {
def mask(path: Path): String = MaskedPath(path).applyMasking()
logger.trace(
s"Local library search paths = ${localLibrarySearchPaths.map(mask)}"
)
logger.trace(
s"Primary library cache = ${mask(cacheRoot)}"
)
logger.trace(
s"Auxiliary (bundled) library caches = " +
s"${additionalCacheLocations.map(mask)}"
)
}
/** Resolves the library version that should be used based on the
* configuration and returns its location on the filesystem.
*
@ -124,3 +76,51 @@ class DefaultLibraryProvider(
}
}
}
object DefaultLibraryProvider {
/** Creates a [[ResolvingLibraryProvider]] that can download new libraries.
*
* @param distributionManager a distribution manager
* @param resourceManager a resource manager
* @param lockUserInterface an interface that will handle notifications
* about waiting on locks
* @param progressReporter an interface that will handle progress
* notifications
* @param languageHome a language home which may contain bundled libraries
* @param edition the edition used in the project
* @param preferLocalLibraries project setting whether to use local libraries
*/
def make(
distributionManager: DistributionManager,
resourceManager: ResourceManager,
lockUserInterface: LockUserInterface,
progressReporter: ProgressReporter,
languageHome: Option[LanguageHome],
edition: Editions.ResolvedEdition,
preferLocalLibraries: Boolean
): ResolvingLibraryProvider = {
val locations = LibraryLocations.resolve(distributionManager, languageHome)
val primaryCache = new DownloadingLibraryCache(
locations.primaryCacheRoot,
TemporaryDirectoryManager(distributionManager, resourceManager),
resourceManager,
lockUserInterface,
progressReporter
)
val additionalCaches =
locations.additionalCacheRoots.map(new LocalReadOnlyRepository(_))
val localLibraryProvider =
new DefaultLocalLibraryProvider(locations.localLibrarySearchPaths)
val publishedLibraryProvider =
new DefaultPublishedLibraryProvider(primaryCache, additionalCaches)
new DefaultLibraryProvider(
localLibraryProvider,
publishedLibraryProvider,
edition,
preferLocalLibraries
)
}
}

View File

@ -0,0 +1,64 @@
package org.enso.librarymanager
import com.typesafe.scalalogging.Logger
import org.enso.distribution.{DistributionManager, LanguageHome}
import org.enso.logger.masking.MaskedPath
import java.nio.file.Path
/** Organizes locations which may hold libraries.
*
* @param localLibrarySearchPaths search paths of local (unpublished) libraries
* @param primaryCacheRoot the primary cache, which is the location to which
* new libraries will be downloaded
* @param additionalCacheRoots additional caches, for example libraries
* bundled with an engine release
*/
case class LibraryLocations(
localLibrarySearchPaths: List[Path],
primaryCacheRoot: Path,
additionalCacheRoots: List[Path]
)
object LibraryLocations {
private lazy val logger = Logger[LibraryLocations]
/** Resolves the [[LibraryLocations]] based on the [[DistributionManager]]
* which provides paths to the distribution and an optional [[LanguageHome]]
* which can provide paths to libraries bundled with the current language
* version.
*/
def resolve(
distributionManager: DistributionManager,
languageHome: Option[LanguageHome]
): LibraryLocations = {
val localLibrarySearchPaths =
distributionManager.paths.localLibrariesSearchPaths.toList
val cacheRoot = distributionManager.paths.cachedLibraries
val additionalCacheLocations = {
val engineBundleRoot = languageHome.map(_.libraries)
val locations =
engineBundleRoot.toList ++ distributionManager.auxiliaryLibraryCaches()
locations.distinct
}
def mask(path: Path): String = MaskedPath(path).applyMasking()
logger.trace(
s"Local library search paths = ${localLibrarySearchPaths.map(mask)}"
)
logger.trace(
s"Primary library cache = ${mask(cacheRoot)}"
)
logger.trace(
s"Auxiliary (bundled) library caches = " +
s"${additionalCacheLocations.map(mask)}"
)
LibraryLocations(
localLibrarySearchPaths = localLibrarySearchPaths,
primaryCacheRoot = cacheRoot,
additionalCacheRoots = additionalCacheLocations
)
}
}

View File

@ -2,6 +2,7 @@ package org.enso.librarymanager.local
import com.typesafe.scalalogging.Logger
import org.enso.editions.LibraryName
import org.enso.librarymanager.LibraryLocations
import org.enso.logger.masking.MaskedPath
import java.nio.file.{Files, Path}
@ -48,3 +49,12 @@ class DefaultLocalLibraryProvider(searchPaths: List[Path])
case Nil => None
}
}
object DefaultLocalLibraryProvider {
/** Creates a [[DefaultLocalLibraryProvider]] from the [[LibraryLocations]]
* configuration.
*/
def make(locations: LibraryLocations): DefaultLocalLibraryProvider =
new DefaultLocalLibraryProvider(locations.localLibrarySearchPaths)
}

View File

@ -0,0 +1,58 @@
package org.enso.librarymanager.published
import nl.gn0s1s.bump.SemVer
import org.enso.editions.{Editions, LibraryName}
import org.enso.librarymanager.LibraryResolutionError
import org.enso.librarymanager.published.cache.ReadOnlyLibraryCache
import java.nio.file.Path
import scala.annotation.tailrec
import scala.util.Try
/** A [[PublishedLibraryProvider]] that just provides libraries which are
* already available in the cache.
*/
class CachedLibraryProvider(caches: List[ReadOnlyLibraryCache])
extends PublishedLibraryProvider
with PublishedLibraryCache {
@tailrec
private def findCachedHelper(
libraryName: LibraryName,
version: SemVer,
caches: List[ReadOnlyLibraryCache]
): Option[Path] = caches match {
case head :: tail =>
head.findCachedLibrary(libraryName, version) match {
case Some(found) => Some(found)
case None => findCachedHelper(libraryName, version, tail)
}
case Nil => None
}
/** Looks for the library in the known caches. */
final protected def findCached(
libraryName: LibraryName,
version: SemVer
): Option[Path] = findCachedHelper(libraryName, version, caches)
/** @inheritdoc */
override def findLibrary(
libraryName: LibraryName,
version: SemVer,
recommendedRepository: Editions.Repository
): Try[Path] =
findCached(libraryName, version)
.toRight(
LibraryResolutionError(
s"Library [$libraryName:$version] was not found in the cache."
)
)
.toTry
/** @inheritdoc */
override def isLibraryCached(
libraryName: LibraryName,
version: SemVer
): Boolean = findCached(libraryName, version).isDefined
}

View File

@ -9,7 +9,6 @@ import org.enso.librarymanager.published.cache.{
}
import java.nio.file.Path
import scala.annotation.tailrec
import scala.util.{Success, Try}
/** A default implementation of [[PublishedLibraryProvider]] which uses one
@ -19,24 +18,8 @@ import scala.util.{Success, Try}
class DefaultPublishedLibraryProvider(
primaryCache: LibraryCache,
auxiliaryCaches: List[ReadOnlyLibraryCache]
) extends PublishedLibraryProvider {
) extends CachedLibraryProvider(caches = primaryCache :: auxiliaryCaches) {
private val logger = Logger[DefaultPublishedLibraryProvider]
private val caches: List[ReadOnlyLibraryCache] =
primaryCache :: auxiliaryCaches
@tailrec
private def findCached(
libraryName: LibraryName,
version: SemVer,
caches: List[ReadOnlyLibraryCache]
): Option[Path] = caches match {
case head :: tail =>
head.findCachedLibrary(libraryName, version) match {
case Some(found) => Some(found)
case None => findCached(libraryName, version, tail)
}
case Nil => None
}
/** @inheritdoc */
override def findLibrary(
@ -44,7 +27,7 @@ class DefaultPublishedLibraryProvider(
version: SemVer,
recommendedRepository: Editions.Repository
): Try[Path] = {
val cached = findCached(libraryName, version, caches)
val cached = findCached(libraryName, version)
cached.map(Success(_)).getOrElse {
logger.trace(
s"$libraryName was not found in any caches, it will need to be " +

View File

@ -0,0 +1,47 @@
package org.enso.librarymanager.published
import nl.gn0s1s.bump.SemVer
import org.enso.editions.LibraryName
import org.enso.librarymanager.LibraryLocations
import org.enso.librarymanager.published.bundles.LocalReadOnlyRepository
import java.nio.file.Path
/** An interface that allows to check if a given published library version is
* cached.
*/
trait PublishedLibraryCache {
/** Checks if the library at the specific version is already available in the
* caches.
*/
def isLibraryCached(libraryName: LibraryName, version: SemVer): Boolean
}
object PublishedLibraryCache {
/** Creates a read only [[PublishedLibraryCache]] which can be used to check
* which libraries are already cached.
*/
def makeReadOnlyCache(cacheLocations: List[Path]): PublishedLibraryCache =
new CachedLibraryProvider(
cacheLocations.map(new LocalReadOnlyRepository(_))
)
/** Creates a read only [[PublishedLibraryCache]] which can be used to check
* which libraries are already cached.
*
* This function creates a [[LocalReadOnlyRepository]] at the primary cache
* location, which, as described in the documentation of that class, is
* usually not recommended. This situation is however an exception - the
* [[PublishedLibraryCache]] is only used to check if a library is cached or
* not - so even if it responds with 'true' regarding a library that is still
* being installed, any actual access of the library (an attempt to load it)
* will use a proper synchronized cache instance and so will have to wait
* until that installation is complete.
*/
def makeReadOnlyCache(locations: LibraryLocations): PublishedLibraryCache =
makeReadOnlyCache(
locations.primaryCacheRoot :: locations.additionalCacheRoots
)
}

View File

@ -19,6 +19,10 @@ import java.nio.file.{Files, Path}
* actions). So this class performs no synchronization and in the second case
* it is the user's case to not import libraries that are in the middle of
* being copied into this repository.
*
* Usually, this implementation should not be used for the primary cache, as
* other processes can concurrently access it, so the access should be
* synchronized.
*/
class LocalReadOnlyRepository(root: Path) extends ReadOnlyLibraryCache {
private val logger = Logger[LocalReadOnlyRepository]