mirror of
https://github.com/enso-org/enso.git
synced 2024-08-16 16:30:33 +03:00
Add API for component groups (#3286)
This commit is contained in:
parent
2ae636f63c
commit
3858ae7517
@ -12,6 +12,9 @@
|
|||||||
- Updated to
|
- Updated to
|
||||||
[GraalVM 21.3.0](https://github.com/graalvm/graalvm-ce-builds/releases/tag/vm-21.3.0)
|
[GraalVM 21.3.0](https://github.com/graalvm/graalvm-ce-builds/releases/tag/vm-21.3.0)
|
||||||
([#3258](https://github.com/enso-org/enso/pull/3258)).
|
([#3258](https://github.com/enso-org/enso/pull/3258)).
|
||||||
|
- Extended language server API to allow accessing the package definition, and to
|
||||||
|
get the available component groups
|
||||||
|
[#3286](https://github.com/enso-org/enso/pull/3286).
|
||||||
|
|
||||||
## Interpreter/Runtime
|
## Interpreter/Runtime
|
||||||
|
|
||||||
|
@ -62,6 +62,12 @@ transport formats, please look [here](./protocol-architecture).
|
|||||||
- [`LibraryVersion`](#libraryversion)
|
- [`LibraryVersion`](#libraryversion)
|
||||||
- [`Contact`](#contact)
|
- [`Contact`](#contact)
|
||||||
- [`EditionReference`](#editionreference)
|
- [`EditionReference`](#editionreference)
|
||||||
|
- [`ComponentGroups`](#componentgroups)
|
||||||
|
- [`ComponentGroup`](#componentgroup)
|
||||||
|
- [`ExtendedComponentGroup`](#extendedcomponentgroup)
|
||||||
|
- [`ModuleReference`](#modulereference)
|
||||||
|
- [`Component`](#component)
|
||||||
|
- [`LibraryComponentGroup`](#librarycomponentgroup)
|
||||||
- [Connection Management](#connection-management)
|
- [Connection Management](#connection-management)
|
||||||
- [`session/initProtocolConnection`](#sessioninitprotocolconnection)
|
- [`session/initProtocolConnection`](#sessioninitprotocolconnection)
|
||||||
- [`session/initBinaryConnection`](#sessioninitbinaryconnection)
|
- [`session/initBinaryConnection`](#sessioninitbinaryconnection)
|
||||||
@ -156,10 +162,12 @@ transport formats, please look [here](./protocol-architecture).
|
|||||||
- [`editions/setProjectParentEdition`](#editionssetprojectparentedition)
|
- [`editions/setProjectParentEdition`](#editionssetprojectparentedition)
|
||||||
- [`editions/setProjectLocalLibrariesPreference`](#editionssetprojectlocallibrariespreference)
|
- [`editions/setProjectLocalLibrariesPreference`](#editionssetprojectlocallibrariespreference)
|
||||||
- [`editions/listDefinedLibraries`](#editionslistdefinedlibraries)
|
- [`editions/listDefinedLibraries`](#editionslistdefinedlibraries)
|
||||||
|
- [`editions/listDefinedComponents`](#editionslistdefinedcomponents)
|
||||||
- [`library/listLocal`](#librarylistlocal)
|
- [`library/listLocal`](#librarylistlocal)
|
||||||
- [`library/create`](#librarycreate)
|
- [`library/create`](#librarycreate)
|
||||||
- [`library/getMetadata`](#librarygetmetadata)
|
- [`library/getMetadata`](#librarygetmetadata)
|
||||||
- [`library/setMetadata`](#librarysetmetadata)
|
- [`library/setMetadata`](#librarysetmetadata)
|
||||||
|
- [`library/getPackage`](#librarygetpackage)
|
||||||
- [`library/publish`](#librarypublish)
|
- [`library/publish`](#librarypublish)
|
||||||
- [`library/preinstall`](#librarypreinstall)
|
- [`library/preinstall`](#librarypreinstall)
|
||||||
- [Errors](#errors-75)
|
- [Errors](#errors-75)
|
||||||
@ -204,6 +212,7 @@ transport formats, please look [here](./protocol-architecture).
|
|||||||
- [`LibraryNotResolved`](#librarynotresolved)
|
- [`LibraryNotResolved`](#librarynotresolved)
|
||||||
- [`InvalidLibraryName`](#invalidlibraryname)
|
- [`InvalidLibraryName`](#invalidlibraryname)
|
||||||
- [`DependencyDiscoveryError`](#dependencydiscoveryerror)
|
- [`DependencyDiscoveryError`](#dependencydiscoveryerror)
|
||||||
|
- [`InvalidSemverVersion`](#invalidsemverversion)
|
||||||
|
|
||||||
<!-- /MarkdownTOC -->
|
<!-- /MarkdownTOC -->
|
||||||
|
|
||||||
@ -1418,6 +1427,114 @@ interface NamedEdition {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### `ComponentGroups`
|
||||||
|
|
||||||
|
The description of component groups provided by the package. Object fields can
|
||||||
|
be omitted if the corresponding list is empty.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface ComponentGroups {
|
||||||
|
/** The list of component groups provided by the package. */
|
||||||
|
newGroups?: ComponentGroup[];
|
||||||
|
|
||||||
|
/** The list of component groups that this package extends.*/
|
||||||
|
extendedGroups?: ExtendedComponentGroup[];
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### `ComponentGroup`
|
||||||
|
|
||||||
|
The definition of a single component group.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface ComponentGroup {
|
||||||
|
/** The module name containing the declared componennts. */
|
||||||
|
module: string;
|
||||||
|
|
||||||
|
color?: string;
|
||||||
|
|
||||||
|
icon?: string;
|
||||||
|
|
||||||
|
/** The list of components provided by this component group. */
|
||||||
|
exports: Component[];
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### `ExtendedComponentGroup`
|
||||||
|
|
||||||
|
The definition of a component group that extends an existing one.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface ExtendedComponentGroup {
|
||||||
|
/** The reference to the component group module being extended. */
|
||||||
|
module: ModuleReference;
|
||||||
|
|
||||||
|
/** The list of components provided by this component group. */
|
||||||
|
exports: Component[];
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### `ModuleReference`
|
||||||
|
|
||||||
|
The reference to a module.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface ModuleReference {
|
||||||
|
/**
|
||||||
|
* A string consisting of a namespace and a lirary name separated by the dot
|
||||||
|
* <namespace>.<library name>, i.e. `Standard.Base`.
|
||||||
|
*/
|
||||||
|
libraryName: string;
|
||||||
|
|
||||||
|
/** The module name without the library name prefix.
|
||||||
|
* E.g. given the `Standard.Base.Data.Vector` module reference,
|
||||||
|
* the `moduleName` field contains `Data.Vector`.
|
||||||
|
*/
|
||||||
|
moduleName: string;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### `Component`
|
||||||
|
|
||||||
|
A single component of a component group.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface Component {
|
||||||
|
/** The component name. */
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
/** The component shortcut. */
|
||||||
|
shortcut?: string;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### `LibraryComponentGroup`
|
||||||
|
|
||||||
|
The component group provided by a library.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface LibraryComponentGroup {
|
||||||
|
/**
|
||||||
|
* A string consisting of a namespace and a lirary name separated by the dot
|
||||||
|
* <namespace>.<library name>, i.e. `Standard.Base`.
|
||||||
|
*/
|
||||||
|
library: string;
|
||||||
|
|
||||||
|
/** The module name without the library name prefix.
|
||||||
|
* E.g. given the `Standard.Base.Data.Vector` module reference,
|
||||||
|
* the `module` field contains `Data.Vector`.
|
||||||
|
*/
|
||||||
|
module: string;
|
||||||
|
|
||||||
|
color?: string;
|
||||||
|
|
||||||
|
icon?: string;
|
||||||
|
|
||||||
|
/** The list of components provided by this component group. */
|
||||||
|
exports: Component[];
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## Connection Management
|
## Connection Management
|
||||||
|
|
||||||
In order to properly set-up and tear-down the language server connection, we
|
In order to properly set-up and tear-down the language server connection, we
|
||||||
@ -4305,6 +4422,33 @@ To get local libraries that are not directly referenced in the edition, use
|
|||||||
|
|
||||||
#### Errors
|
#### Errors
|
||||||
|
|
||||||
|
- [`EditionNotFoundError`](#editionnotfounderror) indicates that the requested
|
||||||
|
edition, or an edition referenced in one of its parents, could not be found.
|
||||||
|
- [`FileSystemError`](#filesystemerror) to signal a generic, unrecoverable
|
||||||
|
file-system error.
|
||||||
|
|
||||||
|
### `editions/listDefinedComponents`
|
||||||
|
|
||||||
|
Lists all the component groups defined in an edition.
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
{
|
||||||
|
edition: EditionReference;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Result
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
{
|
||||||
|
availableComponents: LibraryComponentGroup[];
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Errors
|
||||||
|
|
||||||
- [`EditionNotFoundError`](#editionnotfounderror) indicates that the requested
|
- [`EditionNotFoundError`](#editionnotfounderror) indicates that the requested
|
||||||
edition, or an edition referenced in one of its parents, could not be found.
|
edition, or an edition referenced in one of its parents, could not be found.
|
||||||
- [`FileSystemError`](#filesystemerror) to signal a generic, unrecoverable
|
- [`FileSystemError`](#filesystemerror) to signal a generic, unrecoverable
|
||||||
@ -4412,6 +4556,8 @@ All returned fields are optional, as they may be missing.
|
|||||||
|
|
||||||
- [`LocalLibraryNotFound`](#locallibrarynotfound) to signal that a local library
|
- [`LocalLibraryNotFound`](#locallibrarynotfound) to signal that a local library
|
||||||
with the given name does not exist on the local libraries path.
|
with the given name does not exist on the local libraries path.
|
||||||
|
- [`InvalidSemverVersion`](#invalidsemverversion) to signal that the provided
|
||||||
|
version string is not a valid semver version.
|
||||||
- [`FileSystemError`](#filesystemerror) to signal a generic, unrecoverable
|
- [`FileSystemError`](#filesystemerror) to signal a generic, unrecoverable
|
||||||
file-system error.
|
file-system error.
|
||||||
|
|
||||||
@ -4446,6 +4592,47 @@ null;
|
|||||||
- [`FileSystemError`](#filesystemerror) to signal a generic, unrecoverable
|
- [`FileSystemError`](#filesystemerror) to signal a generic, unrecoverable
|
||||||
file-system error.
|
file-system error.
|
||||||
|
|
||||||
|
### `library/getPackage`
|
||||||
|
|
||||||
|
Gets the package config associated with a specific library version.
|
||||||
|
|
||||||
|
If the version is `LocalLibraryVersion`, it will try to read the package file of
|
||||||
|
the local library and return an empty result if the manifest does not exist.
|
||||||
|
|
||||||
|
If the version is `PublishedLibraryVersion`, it will fetch the package config
|
||||||
|
from the library repository. A cached package config may also be used, if it is
|
||||||
|
available.
|
||||||
|
|
||||||
|
All returned fields are optional, as they may be missing.
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
{
|
||||||
|
namespace: String;
|
||||||
|
name: String;
|
||||||
|
version: LibraryVersion;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Results
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
{
|
||||||
|
license?: String;
|
||||||
|
componentGroups?: ComponentGroups;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Errors
|
||||||
|
|
||||||
|
- [`LocalLibraryNotFound`](#locallibrarynotfound) to signal that a local library
|
||||||
|
with the given name does not exist on the local libraries path.
|
||||||
|
- [`InvalidSemverVersion`](#invalidsemverversion) to signal that the provided
|
||||||
|
version string is not a valid semver version.
|
||||||
|
- [`FileSystemError`](#filesystemerror) to signal a generic, unrecoverable
|
||||||
|
file-system error.
|
||||||
|
|
||||||
### `library/publish`
|
### `library/publish`
|
||||||
|
|
||||||
Publishes a library located in the local libraries directory to the main Enso
|
Publishes a library located in the local libraries directory to the main Enso
|
||||||
@ -5083,3 +5270,18 @@ dependencies of the requested library.
|
|||||||
"message" : "Error occurred while discovering dependencies: <reason>."
|
"message" : "Error occurred while discovering dependencies: <reason>."
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### `InvalidSemverVersion`
|
||||||
|
|
||||||
|
Signals that the provided version string is not a valid semver version. The
|
||||||
|
message contains the invalid version in the payload.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
"error" : {
|
||||||
|
"code" : 8011,
|
||||||
|
"message" : "[<invalid-version>] is not a valid semver version.",
|
||||||
|
"payload" : {
|
||||||
|
"version" : "<invalid-version>"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
@ -0,0 +1,196 @@
|
|||||||
|
package org.enso.languageserver.libraries
|
||||||
|
|
||||||
|
import org.enso.editions.LibraryName
|
||||||
|
import org.enso.pkg.{
|
||||||
|
ComponentGroup,
|
||||||
|
ComponentGroups,
|
||||||
|
Config,
|
||||||
|
ExtendedComponentGroup,
|
||||||
|
ModuleReference
|
||||||
|
}
|
||||||
|
|
||||||
|
import scala.collection.immutable.ListMap
|
||||||
|
import scala.collection.{mutable, View}
|
||||||
|
|
||||||
|
/** The module allowing to resolve the dependencies between the component groups
|
||||||
|
* of different packages.
|
||||||
|
*/
|
||||||
|
final class ComponentGroupsResolver {
|
||||||
|
|
||||||
|
/** Run the component groups resolution algorithm.
|
||||||
|
*
|
||||||
|
* A package can define a new component group or extend an existing one. The
|
||||||
|
* resolving algorithm takes the component groups defined by the packages and
|
||||||
|
* applies the available extensions.
|
||||||
|
*
|
||||||
|
* @param packages the list of package configs
|
||||||
|
* @return the list of component groups with the dependencies resolved
|
||||||
|
*/
|
||||||
|
def run(packages: Iterable[Config]): Vector[LibraryComponentGroup] = {
|
||||||
|
val libraryComponents = packages
|
||||||
|
.map { config =>
|
||||||
|
LibraryName(config.namespace, config.name) -> config.componentGroups
|
||||||
|
}
|
||||||
|
.collect { case (libraryName, Right(componentGroups)) =>
|
||||||
|
libraryName -> componentGroups
|
||||||
|
}
|
||||||
|
val libraryComponentsMap =
|
||||||
|
ComponentGroupsResolver
|
||||||
|
.toMapKeepFirst(libraryComponents)
|
||||||
|
.to(ListMap)
|
||||||
|
resolveComponentGroups(libraryComponentsMap)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Resolve the component groups. Utility method that takes a list of
|
||||||
|
* component groups associated with the library name.
|
||||||
|
*
|
||||||
|
* @param libraryComponents the associated list of component groups
|
||||||
|
* @return the list of component groups with dependencies resolved
|
||||||
|
*/
|
||||||
|
private def resolveComponentGroups(
|
||||||
|
libraryComponents: Map[LibraryName, ComponentGroups]
|
||||||
|
): Vector[LibraryComponentGroup] = {
|
||||||
|
val newLibraryComponentGroups: View[LibraryComponentGroup] =
|
||||||
|
libraryComponents.view
|
||||||
|
.flatMap { case (libraryName, componentGroups) =>
|
||||||
|
componentGroups.newGroups.map(toLibraryComponentGroup(libraryName, _))
|
||||||
|
}
|
||||||
|
val newLibraryComponentGroupsMap
|
||||||
|
: Map[ModuleReference, LibraryComponentGroup] =
|
||||||
|
ComponentGroupsResolver
|
||||||
|
.groupByKeepFirst(newLibraryComponentGroups) { libraryComponentGroup =>
|
||||||
|
ModuleReference(
|
||||||
|
libraryComponentGroup.library,
|
||||||
|
libraryComponentGroup.module
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val extendedComponentGroups: View[ExtendedComponentGroup] =
|
||||||
|
libraryComponents.view
|
||||||
|
.flatMap { case (_, componentGroups) =>
|
||||||
|
componentGroups.extendedGroups
|
||||||
|
}
|
||||||
|
val extendedComponentGroupsMap
|
||||||
|
: Map[ModuleReference, Vector[ExtendedComponentGroup]] =
|
||||||
|
ComponentGroupsResolver
|
||||||
|
.groupByKeepOrder(extendedComponentGroups)(_.module)
|
||||||
|
|
||||||
|
applyExtendedComponentGroups(
|
||||||
|
newLibraryComponentGroupsMap,
|
||||||
|
extendedComponentGroupsMap
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Applies the extended component groups to the existing ones.
|
||||||
|
*
|
||||||
|
* @param libraryComponentGroups the list of component groups defined by
|
||||||
|
* packages
|
||||||
|
* @param extendedComponentGroups the list of component groups extending
|
||||||
|
* existing ones
|
||||||
|
* @return the list of component groups after extended component groups being
|
||||||
|
* applied
|
||||||
|
*/
|
||||||
|
private def applyExtendedComponentGroups(
|
||||||
|
libraryComponentGroups: Map[ModuleReference, LibraryComponentGroup],
|
||||||
|
extendedComponentGroups: Map[ModuleReference, Seq[ExtendedComponentGroup]]
|
||||||
|
): Vector[LibraryComponentGroup] =
|
||||||
|
libraryComponentGroups.map { case (module, libraryComponentGroup) =>
|
||||||
|
extendedComponentGroups
|
||||||
|
.get(module)
|
||||||
|
.fold(libraryComponentGroup) { extendedComponentGroups =>
|
||||||
|
extendedComponentGroups
|
||||||
|
.foldLeft(libraryComponentGroup)(applyExtendedComponentGroup)
|
||||||
|
}
|
||||||
|
}.toVector
|
||||||
|
|
||||||
|
/** Applies the extended component group to the target component group.
|
||||||
|
*
|
||||||
|
* @param libraryComponentGroup the target component group
|
||||||
|
* @param extendedComponentGroup the component group to apply
|
||||||
|
* @return the resulting component group
|
||||||
|
*/
|
||||||
|
private def applyExtendedComponentGroup(
|
||||||
|
libraryComponentGroup: LibraryComponentGroup,
|
||||||
|
extendedComponentGroup: ExtendedComponentGroup
|
||||||
|
): LibraryComponentGroup =
|
||||||
|
libraryComponentGroup.copy(
|
||||||
|
exports = libraryComponentGroup.exports :++ extendedComponentGroup.exports
|
||||||
|
)
|
||||||
|
|
||||||
|
/** Convert [[ComponentGroup]] to [[LibraryComponentGroup]] representation.
|
||||||
|
*
|
||||||
|
* @param libraryName the library name defining this component group
|
||||||
|
* @param componentGroup the component group to convert
|
||||||
|
* @return the [[LibraryComponentGroup]] representation of this component
|
||||||
|
* group
|
||||||
|
*/
|
||||||
|
private def toLibraryComponentGroup(
|
||||||
|
libraryName: LibraryName,
|
||||||
|
componentGroup: ComponentGroup
|
||||||
|
): LibraryComponentGroup = {
|
||||||
|
LibraryComponentGroup(
|
||||||
|
libraryName,
|
||||||
|
componentGroup.module,
|
||||||
|
componentGroup.color,
|
||||||
|
componentGroup.icon,
|
||||||
|
componentGroup.exports
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
object ComponentGroupsResolver {
|
||||||
|
|
||||||
|
/** Partitions this collection into a map according to some discriminator
|
||||||
|
* function, dropping duplicated keys, and preserving the order of elements.
|
||||||
|
* E.g. if the discriminator function produces same keys for different
|
||||||
|
* values, the resulting map will contain only the first encountered
|
||||||
|
* key-value pair for that key.
|
||||||
|
*
|
||||||
|
* @param xs the source collection
|
||||||
|
* @param f the discriminator function
|
||||||
|
* @return the grouped collection preserving the order of elements
|
||||||
|
*/
|
||||||
|
private def groupByKeepFirst[K, V](xs: Iterable[V])(f: V => K): Map[K, V] =
|
||||||
|
xs
|
||||||
|
.foldLeft(mutable.LinkedHashMap.empty[K, V]) { (m, v) =>
|
||||||
|
val k = f(v)
|
||||||
|
if (m.contains(k)) m
|
||||||
|
else m += k -> v
|
||||||
|
}
|
||||||
|
.toMap
|
||||||
|
|
||||||
|
/** Partitions this collection into a map according to some discriminator
|
||||||
|
* function and preserving the order of elements.
|
||||||
|
*
|
||||||
|
* @param xs the source collection
|
||||||
|
* @param f the discriminator function
|
||||||
|
* @return the grouped collection that preserves the order of elements
|
||||||
|
*/
|
||||||
|
private def groupByKeepOrder[K, V](
|
||||||
|
xs: Iterable[V]
|
||||||
|
)(f: V => K): Map[K, Vector[V]] =
|
||||||
|
xs
|
||||||
|
.foldLeft(mutable.LinkedHashMap.empty[K, Vector[V]]) { (m, v) =>
|
||||||
|
m.updateWith(f(v)) {
|
||||||
|
case Some(xs) => Some(xs :+ v)
|
||||||
|
case None => Some(Vector(v))
|
||||||
|
}
|
||||||
|
m
|
||||||
|
}
|
||||||
|
.toMap
|
||||||
|
|
||||||
|
/** Convert the collection of key-value pairs into a map, dropping duplicated
|
||||||
|
* keys, and preserving the order of elements.
|
||||||
|
*
|
||||||
|
* @param xs the collection of key-value pairs
|
||||||
|
* @return the map preserving the order of elements
|
||||||
|
*/
|
||||||
|
private def toMapKeepFirst[K, V](xs: Iterable[(K, V)]): Map[K, V] =
|
||||||
|
xs
|
||||||
|
.foldLeft(mutable.LinkedHashMap.empty[K, V]) { case (m, (k, v)) =>
|
||||||
|
if (m.contains(k)) m
|
||||||
|
else m += k -> v
|
||||||
|
}
|
||||||
|
.toMap
|
||||||
|
}
|
@ -0,0 +1,149 @@
|
|||||||
|
package org.enso.languageserver.libraries
|
||||||
|
|
||||||
|
import org.enso.editions.LibraryName
|
||||||
|
import org.enso.pkg.{ComponentGroup, Config, ModuleReference}
|
||||||
|
|
||||||
|
import scala.collection.mutable
|
||||||
|
|
||||||
|
/** Validate the component groups of provided packages. */
|
||||||
|
final class ComponentGroupsValidator {
|
||||||
|
|
||||||
|
import ComponentGroupsValidator.ValidationError
|
||||||
|
|
||||||
|
/** Run the validation.
|
||||||
|
*
|
||||||
|
* The algorithm checks that the provided component groups are consistent:
|
||||||
|
* - Package configs have valid component groups structure
|
||||||
|
* - Packages don't define duplicate component groups
|
||||||
|
* - Packages override existing component groups
|
||||||
|
*
|
||||||
|
* @param packages the list of package configs
|
||||||
|
* @return the validation result for each package
|
||||||
|
*/
|
||||||
|
def validate(
|
||||||
|
packages: Iterable[Config]
|
||||||
|
): Iterable[Either[ValidationError, Config]] = {
|
||||||
|
val init: Iterable[Right[ValidationError, Config]] = packages.map(Right(_))
|
||||||
|
val modulesMap: mutable.Map[ModuleReference, ComponentGroup] = mutable.Map()
|
||||||
|
|
||||||
|
runValidation(init)(
|
||||||
|
validateInvalidComponentGroups,
|
||||||
|
validateDuplicateComponentGroups(modulesMap),
|
||||||
|
validateComponentGroupExtendsNothing(modulesMap)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private def validateInvalidComponentGroups
|
||||||
|
: Config => Either[ValidationError, Config] = { config =>
|
||||||
|
val libraryName = LibraryName(config.namespace, config.name)
|
||||||
|
config.componentGroups match {
|
||||||
|
case Right(_) =>
|
||||||
|
Right(config)
|
||||||
|
case Left(e) =>
|
||||||
|
Left(
|
||||||
|
ValidationError.InvalidComponentGroups(libraryName, e.getMessage())
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private def validateDuplicateComponentGroups(
|
||||||
|
modulesMap: mutable.Map[ModuleReference, ComponentGroup]
|
||||||
|
): Config => Either[ValidationError, Config] = { config =>
|
||||||
|
val libraryName = LibraryName(config.namespace, config.name)
|
||||||
|
config.componentGroups.toOption
|
||||||
|
.flatMap { componentGroups =>
|
||||||
|
componentGroups.newGroups
|
||||||
|
.map { componentGroup =>
|
||||||
|
val moduleReference =
|
||||||
|
ModuleReference(libraryName, componentGroup.module)
|
||||||
|
if (modulesMap.contains(moduleReference)) {
|
||||||
|
Left(
|
||||||
|
ValidationError
|
||||||
|
.DuplicatedComponentGroup(libraryName, moduleReference)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
modulesMap += moduleReference -> componentGroup
|
||||||
|
Right(config)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.find(_.isLeft)
|
||||||
|
}
|
||||||
|
.getOrElse(Right(config))
|
||||||
|
}
|
||||||
|
|
||||||
|
private def validateComponentGroupExtendsNothing(
|
||||||
|
modulesMap: mutable.Map[ModuleReference, ComponentGroup]
|
||||||
|
): Config => Either[ValidationError, Config] = { config =>
|
||||||
|
val libraryName = LibraryName(config.namespace, config.name)
|
||||||
|
config.componentGroups.toOption
|
||||||
|
.flatMap { componentGroups =>
|
||||||
|
componentGroups.extendedGroups
|
||||||
|
.map { extendedComponentGroup =>
|
||||||
|
if (modulesMap.contains(extendedComponentGroup.module)) {
|
||||||
|
Right(config)
|
||||||
|
} else {
|
||||||
|
Left(
|
||||||
|
ValidationError.ComponentGroupExtendsNothing(
|
||||||
|
libraryName,
|
||||||
|
extendedComponentGroup.module
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.find(_.isLeft)
|
||||||
|
}
|
||||||
|
.getOrElse(Right(config))
|
||||||
|
}
|
||||||
|
|
||||||
|
private def runValidation[A, E](xs: Iterable[Either[E, A]])(
|
||||||
|
fs: A => Either[E, A]*
|
||||||
|
): Iterable[Either[E, A]] =
|
||||||
|
fs.foldLeft(xs) { (xs, f) =>
|
||||||
|
xs.map {
|
||||||
|
case Right(a) => f(a)
|
||||||
|
case Left(e) => Left(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object ComponentGroupsValidator {
|
||||||
|
|
||||||
|
/** Base trait for validation results. */
|
||||||
|
sealed trait ValidationError
|
||||||
|
object ValidationError {
|
||||||
|
|
||||||
|
/** An error indicating that the package config defines duplicate component
|
||||||
|
* group.
|
||||||
|
*
|
||||||
|
* @param libraryName the library defining duplicate component group
|
||||||
|
* @param moduleReference the duplicated module reference
|
||||||
|
*/
|
||||||
|
case class DuplicatedComponentGroup(
|
||||||
|
libraryName: LibraryName,
|
||||||
|
moduleReference: ModuleReference
|
||||||
|
) extends ValidationError
|
||||||
|
|
||||||
|
/** An error indicating that the package config has invalid component groups
|
||||||
|
* format.
|
||||||
|
*
|
||||||
|
* @param libraryName the library name defining invalid component groups
|
||||||
|
* @param message the error message
|
||||||
|
*/
|
||||||
|
case class InvalidComponentGroups(
|
||||||
|
libraryName: LibraryName,
|
||||||
|
message: String
|
||||||
|
) extends ValidationError
|
||||||
|
|
||||||
|
/** An error indicating that the library defines a component group extension
|
||||||
|
* that extends non-existent component group.
|
||||||
|
*
|
||||||
|
* @param libraryName the library defining problematic component group
|
||||||
|
* extension
|
||||||
|
* @param moduleReference the module reference to non-existent module
|
||||||
|
*/
|
||||||
|
case class ComponentGroupExtendsNothing(
|
||||||
|
libraryName: LibraryName,
|
||||||
|
moduleReference: ModuleReference
|
||||||
|
) extends ValidationError
|
||||||
|
}
|
||||||
|
}
|
@ -1,10 +1,10 @@
|
|||||||
package org.enso.languageserver.libraries
|
package org.enso.languageserver.libraries
|
||||||
|
|
||||||
import io.circe.Json
|
import io.circe.{Json, JsonObject}
|
||||||
import io.circe.literal.JsonStringContext
|
import io.circe.literal.JsonStringContext
|
||||||
import org.enso.editions.{LibraryName, LibraryVersion}
|
import org.enso.editions.{LibraryName, LibraryVersion}
|
||||||
import org.enso.jsonrpc.{Error, HasParams, HasResult, Method, Unused}
|
import org.enso.jsonrpc.{Error, HasParams, HasResult, Method, Unused}
|
||||||
import org.enso.pkg.Contact
|
import org.enso.pkg.{ComponentGroups, Contact}
|
||||||
|
|
||||||
object LibraryApi {
|
object LibraryApi {
|
||||||
case object EditionsListAvailable extends Method("editions/listAvailable") {
|
case object EditionsListAvailable extends Method("editions/listAvailable") {
|
||||||
@ -98,6 +98,21 @@ object LibraryApi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case object EditionsListDefinedComponents
|
||||||
|
extends Method("editions/listDefinedComponents") { self =>
|
||||||
|
|
||||||
|
case class Params(edition: EditionReference)
|
||||||
|
|
||||||
|
case class Result(availableComponents: Seq[LibraryComponentGroup])
|
||||||
|
|
||||||
|
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 object LibraryListLocal extends Method("library/listLocal") { self =>
|
||||||
|
|
||||||
case class Result(localLibraries: Seq[LibraryEntry])
|
case class Result(localLibraries: Seq[LibraryEntry])
|
||||||
@ -163,6 +178,30 @@ object LibraryApi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case object LibraryGetPackage extends Method("library/getPackage") { self =>
|
||||||
|
|
||||||
|
case class Params(
|
||||||
|
namespace: String,
|
||||||
|
name: String,
|
||||||
|
version: LibraryEntry.LibraryVersion
|
||||||
|
)
|
||||||
|
|
||||||
|
// TODO[DB]: raw package was added to response as a temporary field and
|
||||||
|
// should be removed when the integration with IDE is finished
|
||||||
|
case class Result(
|
||||||
|
license: Option[String],
|
||||||
|
componentGroups: Option[ComponentGroups],
|
||||||
|
raw: JsonObject
|
||||||
|
)
|
||||||
|
|
||||||
|
implicit val hasParams = new HasParams[this.type] {
|
||||||
|
type Params = self.Params
|
||||||
|
}
|
||||||
|
implicit val hasResult = new HasResult[this.type] {
|
||||||
|
type Result = self.Result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
case object LibraryPublish extends Method("library/publish") { self =>
|
case object LibraryPublish extends Method("library/publish") { self =>
|
||||||
|
|
||||||
case class Params(
|
case class Params(
|
||||||
@ -256,4 +295,12 @@ object LibraryApi {
|
|||||||
8010,
|
8010,
|
||||||
s"Error occurred while discovering dependencies: $reason."
|
s"Error occurred while discovering dependencies: $reason."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
case class InvalidSemverVersion(version: String)
|
||||||
|
extends Error(8011, s"[$version] is not a valid semver version.") {
|
||||||
|
|
||||||
|
override def payload: Option[Json] = Some(
|
||||||
|
json"""{ "version" : $version }"""
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,59 @@
|
|||||||
|
package org.enso.languageserver.libraries
|
||||||
|
|
||||||
|
import io.circe._
|
||||||
|
import io.circe.syntax._
|
||||||
|
import org.enso.editions.LibraryName
|
||||||
|
import org.enso.pkg.{Component, ModuleName}
|
||||||
|
|
||||||
|
/** The component group definition of a library.
|
||||||
|
*
|
||||||
|
* @param library the library name
|
||||||
|
* @param module the module name
|
||||||
|
* @param color the component group color
|
||||||
|
* @param icon the component group icon
|
||||||
|
* @param exports the list of components provided by this component group
|
||||||
|
*/
|
||||||
|
case class LibraryComponentGroup(
|
||||||
|
library: LibraryName,
|
||||||
|
module: ModuleName,
|
||||||
|
color: Option[String],
|
||||||
|
icon: Option[String],
|
||||||
|
exports: Seq[Component]
|
||||||
|
)
|
||||||
|
object LibraryComponentGroup {
|
||||||
|
|
||||||
|
/** Fields for use when serializing the [[LibraryComponentGroup]]. */
|
||||||
|
private object Fields {
|
||||||
|
val Library = "library"
|
||||||
|
val Module = "module"
|
||||||
|
val Color = "color"
|
||||||
|
val Icon = "icon"
|
||||||
|
val Exports = "exports"
|
||||||
|
}
|
||||||
|
|
||||||
|
/** [[Encoder]] instance for the [[LibraryComponentGroup]]. */
|
||||||
|
implicit val encoder: Encoder[LibraryComponentGroup] = { componentGroup =>
|
||||||
|
val color = componentGroup.color.map(Fields.Color -> _.asJson)
|
||||||
|
val icon = componentGroup.icon.map(Fields.Icon -> _.asJson)
|
||||||
|
val exports = Option.unless(componentGroup.exports.isEmpty)(
|
||||||
|
Fields.Exports -> componentGroup.exports.asJson
|
||||||
|
)
|
||||||
|
Json.obj(
|
||||||
|
(Fields.Library -> componentGroup.library.asJson) +:
|
||||||
|
(Fields.Module -> componentGroup.module.asJson) +:
|
||||||
|
(color.toSeq ++ icon.toSeq ++ exports.toSeq): _*
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** [[Decoder]] instance for the [[LibraryComponentGroup]]. */
|
||||||
|
implicit val decoder: Decoder[LibraryComponentGroup] = { json =>
|
||||||
|
for {
|
||||||
|
library <- json.get[LibraryName](Fields.Library)
|
||||||
|
module <- json.get[ModuleName](Fields.Module)
|
||||||
|
color <- json.get[Option[String]](Fields.Color)
|
||||||
|
icon <- json.get[Option[String]](Fields.Icon)
|
||||||
|
exports <- json.getOrElse[List[Component]](Fields.Exports)(List())
|
||||||
|
} yield LibraryComponentGroup(library, module, color, icon, exports)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -2,7 +2,6 @@ package org.enso.languageserver.libraries
|
|||||||
|
|
||||||
import akka.actor.Props
|
import akka.actor.Props
|
||||||
import com.typesafe.scalalogging.LazyLogging
|
import com.typesafe.scalalogging.LazyLogging
|
||||||
import org.enso.distribution.FileSystem.PathSyntax
|
|
||||||
import org.enso.distribution.{DistributionManager, FileSystem}
|
import org.enso.distribution.{DistributionManager, FileSystem}
|
||||||
import org.enso.editions.{Editions, LibraryName}
|
import org.enso.editions.{Editions, LibraryName}
|
||||||
import org.enso.languageserver.libraries.LocalLibraryManagerProtocol._
|
import org.enso.languageserver.libraries.LocalLibraryManagerProtocol._
|
||||||
@ -12,11 +11,12 @@ import org.enso.librarymanager.local.{
|
|||||||
}
|
}
|
||||||
import org.enso.librarymanager.published.repository.LibraryManifest
|
import org.enso.librarymanager.published.repository.LibraryManifest
|
||||||
import org.enso.pkg.validation.NameValidation
|
import org.enso.pkg.validation.NameValidation
|
||||||
import org.enso.pkg.{Contact, PackageManager}
|
import org.enso.pkg.{Config, Contact, Package, PackageManager}
|
||||||
import org.enso.yaml.YamlHelper
|
import org.enso.yaml.YamlHelper
|
||||||
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.nio.file.{Files, Path}
|
import java.nio.file.{Files, Path}
|
||||||
|
|
||||||
import scala.util.{Success, Try}
|
import scala.util.{Success, Try}
|
||||||
|
|
||||||
/** An Actor that manages local libraries. */
|
/** An Actor that manages local libraries. */
|
||||||
@ -41,6 +41,8 @@ class LocalLibraryManager(
|
|||||||
tagLine = request.tagLine
|
tagLine = request.tagLine
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
case GetPackage(libraryName) =>
|
||||||
|
startRequest(getPackage(libraryName))
|
||||||
case ListLocalLibraries =>
|
case ListLocalLibraries =>
|
||||||
startRequest(listLocalLibraries())
|
startRequest(listLocalLibraries())
|
||||||
case Create(libraryName, authors, maintainers, license) =>
|
case Create(libraryName, authors, maintainers, license) =>
|
||||||
@ -190,6 +192,28 @@ class LocalLibraryManager(
|
|||||||
_ = saveManifest(manifestPath, updatedManifest)
|
_ = saveManifest(manifestPath, updatedManifest)
|
||||||
} yield EmptyResponse()
|
} yield EmptyResponse()
|
||||||
|
|
||||||
|
/** Loads the package config for a local library. */
|
||||||
|
private def getPackage(libraryName: LibraryName): Try[GetPackageResponse] =
|
||||||
|
for {
|
||||||
|
libraryRootPath <- localLibraryProvider
|
||||||
|
.findLibrary(libraryName)
|
||||||
|
.toRight(LocalLibraryNotFoundError(libraryName))
|
||||||
|
.toTry
|
||||||
|
configPath = libraryRootPath / Package.configFileName
|
||||||
|
config <- loadPackageConfig(configPath)
|
||||||
|
} yield {
|
||||||
|
if (config.componentGroups.isLeft) {
|
||||||
|
logger.error(
|
||||||
|
s"Failed to parse library [$libraryName] component groups."
|
||||||
|
)
|
||||||
|
}
|
||||||
|
GetPackageResponse(
|
||||||
|
license = config.license,
|
||||||
|
componentGroups = config.componentGroups.toOption,
|
||||||
|
rawPackage = config.originalJson
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/** Tries to load the manifest.
|
/** Tries to load the manifest.
|
||||||
*
|
*
|
||||||
* If the file does not exist, an empty manifest is returned. Any other
|
* If the file does not exist, an empty manifest is returned. Any other
|
||||||
@ -206,6 +230,10 @@ class LocalLibraryManager(
|
|||||||
manifest: LibraryManifest
|
manifest: LibraryManifest
|
||||||
): Unit = FileSystem.writeTextFile(manifestPath, YamlHelper.toYaml(manifest))
|
): Unit = FileSystem.writeTextFile(manifestPath, YamlHelper.toYaml(manifest))
|
||||||
|
|
||||||
|
/** Load the package config. */
|
||||||
|
private def loadPackageConfig(packagePath: Path): Try[Config] =
|
||||||
|
YamlHelper.load[Config](packagePath)
|
||||||
|
|
||||||
/** Finds the edition associated with the current project, if specified in its
|
/** Finds the edition associated with the current project, if specified in its
|
||||||
* config.
|
* config.
|
||||||
*/
|
*/
|
||||||
|
@ -0,0 +1,21 @@
|
|||||||
|
package org.enso.languageserver.libraries
|
||||||
|
|
||||||
|
import org.enso.jsonrpc
|
||||||
|
|
||||||
|
/** The object mapping the [[LocalLibraryManagerProtocol]] failures into the
|
||||||
|
* corresponding JSONRPC error messages.
|
||||||
|
*/
|
||||||
|
object LocalLibraryManagerFailureMapper {
|
||||||
|
|
||||||
|
/** Convert the [[LocalLibraryManagerProtocol.Failure]] into the corresponding
|
||||||
|
* JSONRPC error message.
|
||||||
|
*
|
||||||
|
* @param error the failure object
|
||||||
|
* @return the JSONRPC error message
|
||||||
|
*/
|
||||||
|
def mapFailure(error: LocalLibraryManagerProtocol.Failure): jsonrpc.Error =
|
||||||
|
error match {
|
||||||
|
case LocalLibraryManagerProtocol.InvalidSemverVersionError(version) =>
|
||||||
|
LibraryApi.InvalidSemverVersion(version)
|
||||||
|
}
|
||||||
|
}
|
@ -1,9 +1,9 @@
|
|||||||
package org.enso.languageserver.libraries
|
package org.enso.languageserver.libraries
|
||||||
|
|
||||||
|
import io.circe.JsonObject
|
||||||
import org.enso.editions.LibraryName
|
import org.enso.editions.LibraryName
|
||||||
import org.enso.pkg.Contact
|
import org.enso.librarymanager.resolved.LibraryRoot
|
||||||
|
import org.enso.pkg.{ComponentGroups, Contact}
|
||||||
import java.nio.file.Path
|
|
||||||
|
|
||||||
object LocalLibraryManagerProtocol {
|
object LocalLibraryManagerProtocol {
|
||||||
|
|
||||||
@ -26,6 +26,16 @@ object LocalLibraryManagerProtocol {
|
|||||||
tagLine: Option[String]
|
tagLine: Option[String]
|
||||||
) extends Request
|
) extends Request
|
||||||
|
|
||||||
|
/** A request to get the library package. */
|
||||||
|
case class GetPackage(libraryName: LibraryName) extends Request
|
||||||
|
|
||||||
|
/** A response to the [[GetPackage]] request. */
|
||||||
|
case class GetPackageResponse(
|
||||||
|
license: String,
|
||||||
|
componentGroups: Option[ComponentGroups],
|
||||||
|
rawPackage: JsonObject
|
||||||
|
)
|
||||||
|
|
||||||
/** A request to list local libraries. */
|
/** A request to list local libraries. */
|
||||||
case object ListLocalLibraries extends Request
|
case object ListLocalLibraries extends Request
|
||||||
|
|
||||||
@ -44,7 +54,7 @@ object LocalLibraryManagerProtocol {
|
|||||||
case class FindLibrary(libraryName: LibraryName) extends Request
|
case class FindLibrary(libraryName: LibraryName) extends Request
|
||||||
|
|
||||||
/** A response to [[FindLibrary]]. */
|
/** A response to [[FindLibrary]]. */
|
||||||
case class FindLibraryResponse(libraryRoot: Option[Path])
|
case class FindLibraryResponse(libraryRoot: Option[LibraryRoot])
|
||||||
|
|
||||||
/** Indicates that a library with the given name was not found among local
|
/** Indicates that a library with the given name was not found among local
|
||||||
* libraries.
|
* libraries.
|
||||||
@ -59,4 +69,14 @@ object LocalLibraryManagerProtocol {
|
|||||||
* Sent as a reply to [[Create]] and [[SetMetadata]].
|
* Sent as a reply to [[Create]] and [[SetMetadata]].
|
||||||
*/
|
*/
|
||||||
case class EmptyResponse()
|
case class EmptyResponse()
|
||||||
|
|
||||||
|
/** A base trait for failures. */
|
||||||
|
sealed trait Failure
|
||||||
|
|
||||||
|
/** An error indicating that the provided version is not a valid semver
|
||||||
|
* version.
|
||||||
|
*
|
||||||
|
* @version invalid version string
|
||||||
|
*/
|
||||||
|
case class InvalidSemverVersionError(version: String) extends Failure
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,165 @@
|
|||||||
|
package org.enso.languageserver.libraries.handler
|
||||||
|
|
||||||
|
import akka.actor.{Actor, ActorRef, Props, Status}
|
||||||
|
import akka.pattern.pipe
|
||||||
|
import com.typesafe.scalalogging.LazyLogging
|
||||||
|
import org.enso.editions.{LibraryName, LibraryVersion}
|
||||||
|
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.{
|
||||||
|
BlockingOperation,
|
||||||
|
ComponentGroupsResolver,
|
||||||
|
ComponentGroupsValidator,
|
||||||
|
EditionReferenceResolver,
|
||||||
|
LibraryComponentGroup
|
||||||
|
}
|
||||||
|
import org.enso.languageserver.util.UnhandledLogging
|
||||||
|
import org.enso.librarymanager.local.LocalLibraryProvider
|
||||||
|
import org.enso.librarymanager.published.PublishedLibraryCache
|
||||||
|
import org.enso.pkg.Config
|
||||||
|
|
||||||
|
/** A request handler for the `editions/listDefinedComponents` endpoint.
|
||||||
|
*
|
||||||
|
* @param editionReferenceResolver an [[EditionReferenceResolver]] instance
|
||||||
|
* @param localLibraryProvider a provider of local libraries
|
||||||
|
* @param publishedLibraryCache a cache of published libraries
|
||||||
|
* @param componentGroupsResolver a module resolving the dependencies between
|
||||||
|
* component groups
|
||||||
|
*/
|
||||||
|
class EditionsListDefinedComponentsHandler(
|
||||||
|
editionReferenceResolver: EditionReferenceResolver,
|
||||||
|
localLibraryProvider: LocalLibraryProvider,
|
||||||
|
publishedLibraryCache: PublishedLibraryCache,
|
||||||
|
componentGroupsResolver: ComponentGroupsResolver,
|
||||||
|
componentGroupsValidator: ComponentGroupsValidator
|
||||||
|
) extends Actor
|
||||||
|
with LazyLogging
|
||||||
|
with UnhandledLogging {
|
||||||
|
|
||||||
|
import context.dispatcher
|
||||||
|
|
||||||
|
override def receive: Receive = requestStage
|
||||||
|
|
||||||
|
private def requestStage: Receive = {
|
||||||
|
case Request(
|
||||||
|
EditionsListDefinedComponents,
|
||||||
|
id,
|
||||||
|
EditionsListDefinedComponents.Params(reference)
|
||||||
|
) =>
|
||||||
|
BlockingOperation
|
||||||
|
.run {
|
||||||
|
val edition = editionReferenceResolver.resolveEdition(reference).get
|
||||||
|
val definedLibraries = edition.getAllDefinedLibraries.view
|
||||||
|
.map { case (name, version) =>
|
||||||
|
readLocalPackage(name, version)
|
||||||
|
}
|
||||||
|
.collect { case Some(config) =>
|
||||||
|
config
|
||||||
|
}
|
||||||
|
val validationResults = componentGroupsValidator
|
||||||
|
.validate(definedLibraries)
|
||||||
|
|
||||||
|
validationResults
|
||||||
|
.collect { case Left(error) => error }
|
||||||
|
.foreach(logValidationError)
|
||||||
|
|
||||||
|
val validatedLibraries = validationResults
|
||||||
|
.collect { case Right(config) => config }
|
||||||
|
componentGroupsResolver.run(validatedLibraries)
|
||||||
|
}
|
||||||
|
.map(EditionsListDefinedComponentsHandler.Result) pipeTo self
|
||||||
|
|
||||||
|
context.become(responseStage(id, sender()))
|
||||||
|
}
|
||||||
|
|
||||||
|
private def logValidationError(
|
||||||
|
error: ComponentGroupsValidator.ValidationError
|
||||||
|
): Unit =
|
||||||
|
error match {
|
||||||
|
case ComponentGroupsValidator.ValidationError
|
||||||
|
.InvalidComponentGroups(libraryName, message) =>
|
||||||
|
logger.warn(
|
||||||
|
s"Validation error. Failed to read library [$libraryName] " +
|
||||||
|
s"component groups (reason: $message)."
|
||||||
|
)
|
||||||
|
case ComponentGroupsValidator.ValidationError
|
||||||
|
.DuplicatedComponentGroup(libraryName, moduleReference) =>
|
||||||
|
logger.warn(
|
||||||
|
s"Validation error. Library [$libraryName] defines duplicate " +
|
||||||
|
s"component group [$moduleReference]."
|
||||||
|
)
|
||||||
|
case ComponentGroupsValidator.ValidationError
|
||||||
|
.ComponentGroupExtendsNothing(libraryName, moduleReference) =>
|
||||||
|
logger.warn(
|
||||||
|
s"Validation error. Library [$libraryName] component group " +
|
||||||
|
s"[$moduleReference] extends nothing."
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private def responseStage(id: Id, replyTo: ActorRef): Receive = {
|
||||||
|
case EditionsListDefinedComponentsHandler.Result(components) =>
|
||||||
|
replyTo ! ResponseResult(
|
||||||
|
EditionsListDefinedComponents,
|
||||||
|
id,
|
||||||
|
EditionsListDefinedComponents.Result(components)
|
||||||
|
)
|
||||||
|
context.stop(self)
|
||||||
|
|
||||||
|
case Status.Failure(exception) =>
|
||||||
|
replyTo ! ResponseError(
|
||||||
|
Some(id),
|
||||||
|
FileSystemError(exception.getMessage)
|
||||||
|
)
|
||||||
|
context.stop(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
private def readLocalPackage(
|
||||||
|
libraryName: LibraryName,
|
||||||
|
libraryVersion: LibraryVersion
|
||||||
|
): Option[Config] = {
|
||||||
|
val libraryPathOpt = libraryVersion match {
|
||||||
|
case LibraryVersion.Local =>
|
||||||
|
localLibraryProvider.findLibrary(libraryName)
|
||||||
|
case LibraryVersion.Published(version, _) =>
|
||||||
|
publishedLibraryCache.findCachedLibrary(libraryName, version)
|
||||||
|
}
|
||||||
|
libraryPathOpt.flatMap { libraryPath =>
|
||||||
|
libraryPath.getReadAccess.readPackage().toOption
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object EditionsListDefinedComponentsHandler {
|
||||||
|
|
||||||
|
private case class Result(components: Seq[LibraryComponentGroup])
|
||||||
|
|
||||||
|
/** Creates a configuration object to create
|
||||||
|
* [[EditionsListDefinedComponentsHandler]].
|
||||||
|
*
|
||||||
|
* @param editionReferenceResolver an [[EditionReferenceResolver]] instance
|
||||||
|
* @param localLibraryProvider a provider of local libraries
|
||||||
|
* @param publishedLibraryCache a cache of published libraries
|
||||||
|
* @param componentGroupsResolver a module resolving the dependencies
|
||||||
|
* between component groups
|
||||||
|
* @param componentGroupsValidator a module that checks component groups for
|
||||||
|
* consistency
|
||||||
|
*/
|
||||||
|
def props(
|
||||||
|
editionReferenceResolver: EditionReferenceResolver,
|
||||||
|
localLibraryProvider: LocalLibraryProvider,
|
||||||
|
publishedLibraryCache: PublishedLibraryCache,
|
||||||
|
componentGroupsResolver: ComponentGroupsResolver =
|
||||||
|
new ComponentGroupsResolver,
|
||||||
|
componentGroupsValidator: ComponentGroupsValidator =
|
||||||
|
new ComponentGroupsValidator
|
||||||
|
): Props = Props(
|
||||||
|
new EditionsListDefinedComponentsHandler(
|
||||||
|
editionReferenceResolver,
|
||||||
|
localLibraryProvider,
|
||||||
|
publishedLibraryCache,
|
||||||
|
componentGroupsResolver,
|
||||||
|
componentGroupsValidator
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
@ -1,6 +1,8 @@
|
|||||||
package org.enso.languageserver.libraries.handler
|
package org.enso.languageserver.libraries.handler
|
||||||
|
|
||||||
import akka.actor.{Actor, ActorRef, Cancellable, Props, Status}
|
import akka.actor.{Actor, ActorRef, Cancellable, Props, Status}
|
||||||
|
|
||||||
|
import scala.util.{Success, Try}
|
||||||
import akka.pattern.pipe
|
import akka.pattern.pipe
|
||||||
import com.typesafe.scalalogging.LazyLogging
|
import com.typesafe.scalalogging.LazyLogging
|
||||||
import nl.gn0s1s.bump.SemVer
|
import nl.gn0s1s.bump.SemVer
|
||||||
@ -11,23 +13,27 @@ import org.enso.languageserver.filemanager.FileManagerApi.FileSystemError
|
|||||||
import org.enso.languageserver.libraries.LibraryApi._
|
import org.enso.languageserver.libraries.LibraryApi._
|
||||||
import org.enso.languageserver.libraries.{
|
import org.enso.languageserver.libraries.{
|
||||||
LibraryEntry,
|
LibraryEntry,
|
||||||
|
LocalLibraryManagerFailureMapper,
|
||||||
LocalLibraryManagerProtocol
|
LocalLibraryManagerProtocol
|
||||||
}
|
}
|
||||||
import org.enso.languageserver.requesthandler.RequestTimeout
|
import org.enso.languageserver.requesthandler.RequestTimeout
|
||||||
import org.enso.languageserver.util.UnhandledLogging
|
import org.enso.languageserver.util.UnhandledLogging
|
||||||
|
import org.enso.librarymanager.published.PublishedLibraryCache
|
||||||
import org.enso.librarymanager.published.repository.RepositoryHelper.RepositoryMethods
|
import org.enso.librarymanager.published.repository.RepositoryHelper.RepositoryMethods
|
||||||
|
|
||||||
import scala.concurrent.Future
|
import scala.concurrent.Future
|
||||||
import scala.concurrent.duration.FiniteDuration
|
import scala.concurrent.duration.FiniteDuration
|
||||||
|
|
||||||
/** A request handler for the `library/create` endpoint.
|
/** A request handler for the `library/getMetadata` endpoint.
|
||||||
*
|
*
|
||||||
* @param timeout request timeout
|
* @param timeout request timeout
|
||||||
* @param localLibraryManager reference to the local library manager actor
|
* @param localLibraryManager reference to the local library manager actor
|
||||||
|
* @param publishedLibraryCache the cache of published libraries
|
||||||
*/
|
*/
|
||||||
class LibraryGetMetadataHandler(
|
class LibraryGetMetadataHandler(
|
||||||
timeout: FiniteDuration,
|
timeout: FiniteDuration,
|
||||||
localLibraryManager: ActorRef
|
localLibraryManager: ActorRef,
|
||||||
|
publishedLibraryCache: PublishedLibraryCache
|
||||||
) extends Actor
|
) extends Actor
|
||||||
with LazyLogging
|
with LazyLogging
|
||||||
with UnhandledLogging {
|
with UnhandledLogging {
|
||||||
@ -48,11 +54,18 @@ class LibraryGetMetadataHandler(
|
|||||||
libraryName
|
libraryName
|
||||||
)
|
)
|
||||||
case LibraryEntry.PublishedLibraryVersion(version, repositoryUrl) =>
|
case LibraryEntry.PublishedLibraryVersion(version, repositoryUrl) =>
|
||||||
fetchPublishedMetadata(
|
SemVer(version) match {
|
||||||
libraryName,
|
case Some(semVerVersion) =>
|
||||||
version,
|
getOrFetchPublishedMetadata(
|
||||||
repositoryUrl
|
libraryName,
|
||||||
) pipeTo self
|
semVerVersion,
|
||||||
|
repositoryUrl
|
||||||
|
) pipeTo self
|
||||||
|
case None =>
|
||||||
|
self ! LocalLibraryManagerProtocol.InvalidSemverVersionError(
|
||||||
|
version
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val cancellable =
|
val cancellable =
|
||||||
@ -82,33 +95,59 @@ class LibraryGetMetadataHandler(
|
|||||||
cancellable.cancel()
|
cancellable.cancel()
|
||||||
context.stop(self)
|
context.stop(self)
|
||||||
|
|
||||||
|
case failure: LocalLibraryManagerProtocol.Failure =>
|
||||||
|
replyTo ! LocalLibraryManagerFailureMapper.mapFailure(failure)
|
||||||
|
cancellable.cancel()
|
||||||
|
context.stop(self)
|
||||||
|
|
||||||
case Status.Failure(exception) =>
|
case Status.Failure(exception) =>
|
||||||
replyTo ! ResponseError(Some(id), FileSystemError(exception.getMessage))
|
replyTo ! ResponseError(Some(id), FileSystemError(exception.getMessage))
|
||||||
cancellable.cancel()
|
cancellable.cancel()
|
||||||
context.stop(self)
|
context.stop(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO [RW] Once the manifests of downloaded libraries are being cached,
|
private def getOrFetchPublishedMetadata(
|
||||||
// it may be worth to try resolving the local cache first to avoid
|
libraryName: LibraryName,
|
||||||
// downloading the manifest again. This should be done before the issues
|
version: SemVer,
|
||||||
// #1772 or #1775 are completed.
|
repositoryUrl: String
|
||||||
|
): Future[LocalLibraryManagerProtocol.GetMetadataResponse] =
|
||||||
|
getCachedMetadata(libraryName, version) match {
|
||||||
|
case Some(response) =>
|
||||||
|
response.fold(Future.failed, Future.successful)
|
||||||
|
case None =>
|
||||||
|
fetchPublishedMetadata(libraryName, version, repositoryUrl)
|
||||||
|
}
|
||||||
|
|
||||||
|
private def getCachedMetadata(
|
||||||
|
libraryName: LibraryName,
|
||||||
|
version: SemVer
|
||||||
|
): Option[Try[LocalLibraryManagerProtocol.GetMetadataResponse]] =
|
||||||
|
publishedLibraryCache
|
||||||
|
.findCachedLibrary(libraryName, version)
|
||||||
|
.map { libraryPath =>
|
||||||
|
libraryPath.getReadAccess
|
||||||
|
.readManifest()
|
||||||
|
.map { manifestAttempt =>
|
||||||
|
manifestAttempt.map(manifest =>
|
||||||
|
LocalLibraryManagerProtocol.GetMetadataResponse(
|
||||||
|
manifest.description,
|
||||||
|
manifest.tagLine
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.getOrElse(
|
||||||
|
Success(LocalLibraryManagerProtocol.GetMetadataResponse(None, None))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
private def fetchPublishedMetadata(
|
private def fetchPublishedMetadata(
|
||||||
libraryName: LibraryName,
|
libraryName: LibraryName,
|
||||||
version: String,
|
version: SemVer,
|
||||||
repositoryUrl: String
|
repositoryUrl: String
|
||||||
): Future[LocalLibraryManagerProtocol.GetMetadataResponse] = for {
|
): Future[LocalLibraryManagerProtocol.GetMetadataResponse] = for {
|
||||||
semver <- Future.fromTry(
|
|
||||||
SemVer(version)
|
|
||||||
.toRight(
|
|
||||||
new IllegalStateException(
|
|
||||||
s"Library version [$version] is not a valid semver string."
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.toTry
|
|
||||||
)
|
|
||||||
manifest <- Repository(repositoryUrl)
|
manifest <- Repository(repositoryUrl)
|
||||||
.accessLibrary(libraryName, semver)
|
.accessLibrary(libraryName, version)
|
||||||
.downloadManifest()
|
.fetchManifest()
|
||||||
.toFuture
|
.toFuture
|
||||||
} yield LocalLibraryManagerProtocol.GetMetadataResponse(
|
} yield LocalLibraryManagerProtocol.GetMetadataResponse(
|
||||||
description = manifest.description,
|
description = manifest.description,
|
||||||
@ -122,7 +161,18 @@ object LibraryGetMetadataHandler {
|
|||||||
*
|
*
|
||||||
* @param timeout request timeout
|
* @param timeout request timeout
|
||||||
* @param localLibraryManager reference to the local library manager actor
|
* @param localLibraryManager reference to the local library manager actor
|
||||||
|
* @param publishedLibraryCache the cache of published libraries
|
||||||
*/
|
*/
|
||||||
def props(timeout: FiniteDuration, localLibraryManager: ActorRef): Props =
|
def props(
|
||||||
Props(new LibraryGetMetadataHandler(timeout, localLibraryManager))
|
timeout: FiniteDuration,
|
||||||
|
localLibraryManager: ActorRef,
|
||||||
|
publishedLibraryCache: PublishedLibraryCache
|
||||||
|
): Props =
|
||||||
|
Props(
|
||||||
|
new LibraryGetMetadataHandler(
|
||||||
|
timeout,
|
||||||
|
localLibraryManager,
|
||||||
|
publishedLibraryCache
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,179 @@
|
|||||||
|
package org.enso.languageserver.libraries.handler
|
||||||
|
|
||||||
|
import akka.actor.{Actor, ActorRef, Cancellable, Props, Status}
|
||||||
|
import akka.pattern.pipe
|
||||||
|
import com.typesafe.scalalogging.LazyLogging
|
||||||
|
import nl.gn0s1s.bump.SemVer
|
||||||
|
import org.enso.editions.Editions.Repository
|
||||||
|
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.{
|
||||||
|
LibraryEntry,
|
||||||
|
LocalLibraryManagerFailureMapper,
|
||||||
|
LocalLibraryManagerProtocol
|
||||||
|
}
|
||||||
|
import org.enso.languageserver.requesthandler.RequestTimeout
|
||||||
|
import org.enso.languageserver.util.UnhandledLogging
|
||||||
|
import org.enso.librarymanager.published.PublishedLibraryCache
|
||||||
|
import org.enso.librarymanager.published.repository.RepositoryHelper.RepositoryMethods
|
||||||
|
|
||||||
|
import scala.concurrent.Future
|
||||||
|
import scala.concurrent.duration.FiniteDuration
|
||||||
|
import scala.util.Try
|
||||||
|
|
||||||
|
/** A request handler for the `library/getPackage` endpoint.
|
||||||
|
*
|
||||||
|
* @param timeout request timeout
|
||||||
|
* @param localLibraryManager reference to the local library manager actor
|
||||||
|
* @param publishedLibraryCache the cache of published libraries
|
||||||
|
*/
|
||||||
|
class LibraryGetPackageHandler(
|
||||||
|
timeout: FiniteDuration,
|
||||||
|
localLibraryManager: ActorRef,
|
||||||
|
publishedLibraryCache: PublishedLibraryCache
|
||||||
|
) extends Actor
|
||||||
|
with LazyLogging
|
||||||
|
with UnhandledLogging {
|
||||||
|
import context.dispatcher
|
||||||
|
|
||||||
|
override def receive: Receive = requestStage
|
||||||
|
|
||||||
|
private def requestStage: Receive = {
|
||||||
|
case Request(
|
||||||
|
LibraryGetPackage,
|
||||||
|
id,
|
||||||
|
LibraryGetPackage.Params(namespace, name, version)
|
||||||
|
) =>
|
||||||
|
val libraryName = LibraryName(namespace, name)
|
||||||
|
version match {
|
||||||
|
case LibraryEntry.LocalLibraryVersion =>
|
||||||
|
localLibraryManager ! LocalLibraryManagerProtocol.GetPackage(
|
||||||
|
libraryName
|
||||||
|
)
|
||||||
|
case LibraryEntry.PublishedLibraryVersion(version, repositoryUrl) =>
|
||||||
|
SemVer(version) match {
|
||||||
|
case Some(semVerVersion) =>
|
||||||
|
getOrFetchPublishedPackage(
|
||||||
|
libraryName,
|
||||||
|
semVerVersion,
|
||||||
|
repositoryUrl
|
||||||
|
) pipeTo self
|
||||||
|
case None =>
|
||||||
|
self ! LocalLibraryManagerProtocol.InvalidSemverVersionError(
|
||||||
|
version
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 =>
|
||||||
|
logger.error("Request [{}] timed out.", id)
|
||||||
|
replyTo ! ResponseError(Some(id), Errors.RequestTimeout)
|
||||||
|
context.stop(self)
|
||||||
|
|
||||||
|
case LocalLibraryManagerProtocol.GetPackageResponse(
|
||||||
|
license,
|
||||||
|
componentGroups,
|
||||||
|
rawPackage
|
||||||
|
) =>
|
||||||
|
replyTo ! ResponseResult(
|
||||||
|
LibraryGetPackage,
|
||||||
|
id,
|
||||||
|
LibraryGetPackage.Result(
|
||||||
|
Option.unless(license.isEmpty)(license),
|
||||||
|
componentGroups,
|
||||||
|
rawPackage
|
||||||
|
)
|
||||||
|
)
|
||||||
|
cancellable.cancel()
|
||||||
|
context.stop(self)
|
||||||
|
|
||||||
|
case failure: LocalLibraryManagerProtocol.Failure =>
|
||||||
|
replyTo ! LocalLibraryManagerFailureMapper.mapFailure(failure)
|
||||||
|
cancellable.cancel()
|
||||||
|
context.stop(self)
|
||||||
|
|
||||||
|
case Status.Failure(exception) =>
|
||||||
|
replyTo ! ResponseError(Some(id), FileSystemError(exception.getMessage))
|
||||||
|
cancellable.cancel()
|
||||||
|
context.stop(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
private def getOrFetchPublishedPackage(
|
||||||
|
libraryName: LibraryName,
|
||||||
|
version: SemVer,
|
||||||
|
repositoryUrl: String
|
||||||
|
): Future[LocalLibraryManagerProtocol.GetPackageResponse] =
|
||||||
|
getCachedPackage(libraryName, version) match {
|
||||||
|
case Some(response) =>
|
||||||
|
response.fold(Future.failed, Future.successful)
|
||||||
|
case None =>
|
||||||
|
fetchPublishedPackage(libraryName, version, repositoryUrl)
|
||||||
|
}
|
||||||
|
|
||||||
|
private def getCachedPackage(
|
||||||
|
libraryName: LibraryName,
|
||||||
|
version: SemVer
|
||||||
|
): Option[Try[LocalLibraryManagerProtocol.GetPackageResponse]] =
|
||||||
|
publishedLibraryCache
|
||||||
|
.findCachedLibrary(libraryName, version)
|
||||||
|
.map { libraryPath =>
|
||||||
|
libraryPath.getReadAccess
|
||||||
|
.readPackage()
|
||||||
|
.map(config =>
|
||||||
|
LocalLibraryManagerProtocol.GetPackageResponse(
|
||||||
|
config.license,
|
||||||
|
config.componentGroups.toOption,
|
||||||
|
config.originalJson
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private def fetchPublishedPackage(
|
||||||
|
libraryName: LibraryName,
|
||||||
|
version: SemVer,
|
||||||
|
repositoryUrl: String
|
||||||
|
): Future[LocalLibraryManagerProtocol.GetPackageResponse] = for {
|
||||||
|
config <- Repository(repositoryUrl)
|
||||||
|
.accessLibrary(libraryName, version)
|
||||||
|
.fetchPackageConfig()
|
||||||
|
.toFuture
|
||||||
|
} yield LocalLibraryManagerProtocol.GetPackageResponse(
|
||||||
|
license = config.license,
|
||||||
|
componentGroups = config.componentGroups.toOption,
|
||||||
|
rawPackage = config.originalJson
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
object LibraryGetPackageHandler {
|
||||||
|
|
||||||
|
/** Creates a configuration object to create [[LibraryGetPackageHandler]].
|
||||||
|
*
|
||||||
|
* @param timeout request timeout
|
||||||
|
* @param localLibraryManager reference to the local library manager actor
|
||||||
|
* @param publishedLibraryCache the cache of published libraries
|
||||||
|
*/
|
||||||
|
def props(
|
||||||
|
timeout: FiniteDuration,
|
||||||
|
localLibraryManager: ActorRef,
|
||||||
|
publishedLibraryCache: PublishedLibraryCache
|
||||||
|
): Props =
|
||||||
|
Props(
|
||||||
|
new LibraryGetPackageHandler(
|
||||||
|
timeout,
|
||||||
|
localLibraryManager,
|
||||||
|
publishedLibraryCache
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
@ -107,7 +107,7 @@ class LibraryPublishHandler(
|
|||||||
new CompilerBasedDependencyExtractor(logLevel)
|
new CompilerBasedDependencyExtractor(logLevel)
|
||||||
LibraryUploader(dependencyExtractor)
|
LibraryUploader(dependencyExtractor)
|
||||||
.uploadLibrary(
|
.uploadLibrary(
|
||||||
libraryRoot,
|
libraryRoot.location,
|
||||||
uploadUrl,
|
uploadUrl,
|
||||||
token,
|
token,
|
||||||
progressReporter
|
progressReporter
|
||||||
|
@ -73,6 +73,7 @@ import org.enso.polyglot.runtime.Runtime.Api
|
|||||||
import org.enso.polyglot.runtime.Runtime.Api.ProgressNotification
|
import org.enso.polyglot.runtime.Runtime.Api.ProgressNotification
|
||||||
|
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
|
||||||
import scala.concurrent.duration._
|
import scala.concurrent.duration._
|
||||||
|
|
||||||
/** An actor handling communications between a single client and the language
|
/** An actor handling communications between a single client and the language
|
||||||
@ -506,18 +507,33 @@ class JsonConnectionController(
|
|||||||
.props(requestTimeout, projectSettingsManager),
|
.props(requestTimeout, projectSettingsManager),
|
||||||
EditionsSetLocalLibrariesPreference -> EditionsSetProjectLocalLibrariesPreferenceHandler
|
EditionsSetLocalLibrariesPreference -> EditionsSetProjectLocalLibrariesPreferenceHandler
|
||||||
.props(requestTimeout, projectSettingsManager),
|
.props(requestTimeout, projectSettingsManager),
|
||||||
|
EditionsListDefinedComponents -> EditionsListDefinedComponentsHandler
|
||||||
|
.props(
|
||||||
|
libraryConfig.editionReferenceResolver,
|
||||||
|
libraryConfig.localLibraryProvider,
|
||||||
|
libraryConfig.publishedLibraryCache
|
||||||
|
),
|
||||||
LibraryCreate -> LibraryCreateHandler
|
LibraryCreate -> LibraryCreateHandler
|
||||||
.props(requestTimeout, libraryConfig.localLibraryManager),
|
.props(requestTimeout, libraryConfig.localLibraryManager),
|
||||||
LibraryListLocal -> LibraryListLocalHandler
|
LibraryListLocal -> LibraryListLocalHandler
|
||||||
.props(requestTimeout, libraryConfig.localLibraryManager),
|
.props(requestTimeout, libraryConfig.localLibraryManager),
|
||||||
LibraryGetMetadata -> LibraryGetMetadataHandler
|
LibraryGetMetadata -> LibraryGetMetadataHandler
|
||||||
.props(requestTimeout, libraryConfig.localLibraryManager),
|
.props(
|
||||||
|
requestTimeout,
|
||||||
|
libraryConfig.localLibraryManager,
|
||||||
|
libraryConfig.publishedLibraryCache
|
||||||
|
),
|
||||||
LibraryPreinstall -> LibraryPreinstallHandler
|
LibraryPreinstall -> LibraryPreinstallHandler
|
||||||
.props(libraryConfig.editionReferenceResolver, libraryConfig),
|
.props(libraryConfig.editionReferenceResolver, libraryConfig),
|
||||||
LibraryPublish -> LibraryPublishHandler
|
LibraryPublish -> LibraryPublishHandler
|
||||||
.props(requestTimeout, libraryConfig.localLibraryManager),
|
.props(requestTimeout, libraryConfig.localLibraryManager),
|
||||||
LibrarySetMetadata -> LibrarySetMetadataHandler
|
LibrarySetMetadata -> LibrarySetMetadataHandler
|
||||||
.props(requestTimeout, libraryConfig.localLibraryManager)
|
.props(requestTimeout, libraryConfig.localLibraryManager),
|
||||||
|
LibraryGetPackage -> LibraryGetPackageHandler.props(
|
||||||
|
requestTimeout,
|
||||||
|
libraryConfig.localLibraryManager,
|
||||||
|
libraryConfig.publishedLibraryCache
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -76,10 +76,12 @@ object JsonRpc {
|
|||||||
.registerRequest(EditionsSetParentEdition)
|
.registerRequest(EditionsSetParentEdition)
|
||||||
.registerRequest(EditionsSetLocalLibrariesPreference)
|
.registerRequest(EditionsSetLocalLibrariesPreference)
|
||||||
.registerRequest(EditionsListDefinedLibraries)
|
.registerRequest(EditionsListDefinedLibraries)
|
||||||
|
.registerRequest(EditionsListDefinedComponents)
|
||||||
.registerRequest(LibraryListLocal)
|
.registerRequest(LibraryListLocal)
|
||||||
.registerRequest(LibraryCreate)
|
.registerRequest(LibraryCreate)
|
||||||
.registerRequest(LibraryGetMetadata)
|
.registerRequest(LibraryGetMetadata)
|
||||||
.registerRequest(LibrarySetMetadata)
|
.registerRequest(LibrarySetMetadata)
|
||||||
|
.registerRequest(LibraryGetPackage)
|
||||||
.registerRequest(LibraryPublish)
|
.registerRequest(LibraryPublish)
|
||||||
.registerRequest(LibraryPreinstall)
|
.registerRequest(LibraryPreinstall)
|
||||||
.registerNotification(TaskStarted)
|
.registerNotification(TaskStarted)
|
||||||
|
@ -0,0 +1,316 @@
|
|||||||
|
package org.enso.languageserver.libraries
|
||||||
|
|
||||||
|
import org.enso.editions.LibraryName
|
||||||
|
import org.enso.pkg.{
|
||||||
|
Component,
|
||||||
|
ComponentGroup,
|
||||||
|
ComponentGroups,
|
||||||
|
Config,
|
||||||
|
ExtendedComponentGroup,
|
||||||
|
ModuleName,
|
||||||
|
ModuleReference
|
||||||
|
}
|
||||||
|
import org.scalatest.matchers.should.Matchers
|
||||||
|
import org.scalatest.wordspec.AnyWordSpec
|
||||||
|
|
||||||
|
class ComponentGroupsResolverSpec extends AnyWordSpec with Matchers {
|
||||||
|
|
||||||
|
import ComponentGroupsResolverSpec._
|
||||||
|
|
||||||
|
"ComponentGroupsResolver" should {
|
||||||
|
|
||||||
|
"return a list of defined component groups preserving the order" in {
|
||||||
|
val resolver = new ComponentGroupsResolver
|
||||||
|
val testPackages = Vector(
|
||||||
|
config("Foo", "Bar"),
|
||||||
|
config(
|
||||||
|
"Foo",
|
||||||
|
"Baz",
|
||||||
|
ComponentGroups(List(newComponentGroup("Mod1", "one", "two")), List())
|
||||||
|
),
|
||||||
|
config(
|
||||||
|
"Foo",
|
||||||
|
"Quux",
|
||||||
|
ComponentGroups(List(newComponentGroup("Mod2", "one")), List())
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
resolver.run(testPackages) shouldEqual Vector(
|
||||||
|
libraryComponentGroup("Foo", "Baz", "Mod1", "one", "two"),
|
||||||
|
libraryComponentGroup("Foo", "Quux", "Mod2", "one")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
"drop duplicated library definitions" in {
|
||||||
|
val resolver = new ComponentGroupsResolver
|
||||||
|
val testPackages = Vector(
|
||||||
|
config("Foo", "Bar"),
|
||||||
|
config(
|
||||||
|
"Foo",
|
||||||
|
"Baz",
|
||||||
|
ComponentGroups(
|
||||||
|
List(newComponentGroup("Mod1", "one", "two")),
|
||||||
|
List()
|
||||||
|
)
|
||||||
|
),
|
||||||
|
config(
|
||||||
|
"Foo",
|
||||||
|
"Baz",
|
||||||
|
ComponentGroups(List(newComponentGroup("Mod1", "one")), List())
|
||||||
|
),
|
||||||
|
config(
|
||||||
|
"Foo",
|
||||||
|
"Quux",
|
||||||
|
ComponentGroups(List(newComponentGroup("Mod2", "one")), List())
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
resolver.run(testPackages) shouldEqual Vector(
|
||||||
|
libraryComponentGroup("Foo", "Baz", "Mod1", "one", "two"),
|
||||||
|
libraryComponentGroup("Foo", "Quux", "Mod2", "one")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
"drop duplicated component group definitions" in {
|
||||||
|
val resolver = new ComponentGroupsResolver
|
||||||
|
val testPackages = Vector(
|
||||||
|
config("Foo", "Bar"),
|
||||||
|
config(
|
||||||
|
"Foo",
|
||||||
|
"Baz",
|
||||||
|
ComponentGroups(
|
||||||
|
List(
|
||||||
|
newComponentGroup("Mod1", "one", "two"),
|
||||||
|
newComponentGroup("Mod1", "three")
|
||||||
|
),
|
||||||
|
List()
|
||||||
|
)
|
||||||
|
),
|
||||||
|
config(
|
||||||
|
"Foo",
|
||||||
|
"Quux",
|
||||||
|
ComponentGroups(List(newComponentGroup("Mod2", "one")), List())
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
resolver.run(testPackages) shouldEqual Vector(
|
||||||
|
libraryComponentGroup("Foo", "Baz", "Mod1", "one", "two"),
|
||||||
|
libraryComponentGroup("Foo", "Quux", "Mod2", "one")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
"apply extended component groups" in {
|
||||||
|
val resolver = new ComponentGroupsResolver
|
||||||
|
val testPackages = Vector(
|
||||||
|
config(
|
||||||
|
"user",
|
||||||
|
"Unnamed",
|
||||||
|
ComponentGroups(
|
||||||
|
List(newComponentGroup("Main", "main")),
|
||||||
|
List(
|
||||||
|
extendedComponentGroup("Standard", "Base", "Data.Vector", "quux")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
config(
|
||||||
|
"Standard",
|
||||||
|
"Base",
|
||||||
|
ComponentGroups(
|
||||||
|
List(newComponentGroup("Data.Vector", "one", "two")),
|
||||||
|
List()
|
||||||
|
)
|
||||||
|
),
|
||||||
|
config(
|
||||||
|
"user",
|
||||||
|
"Vector_Utils",
|
||||||
|
ComponentGroups(
|
||||||
|
List(),
|
||||||
|
List(
|
||||||
|
extendedComponentGroup("Standard", "Base", "Data.Vector", "three")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
resolver.run(testPackages) shouldEqual Vector(
|
||||||
|
libraryComponentGroup("user", "Unnamed", "Main", "main"),
|
||||||
|
libraryComponentGroup(
|
||||||
|
"Standard",
|
||||||
|
"Base",
|
||||||
|
"Data.Vector",
|
||||||
|
"one",
|
||||||
|
"two",
|
||||||
|
"quux",
|
||||||
|
"three"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
"apply mutually extended component groups" in {
|
||||||
|
val resolver = new ComponentGroupsResolver
|
||||||
|
val testPackages = Vector(
|
||||||
|
config(
|
||||||
|
"Standard",
|
||||||
|
"Table",
|
||||||
|
ComponentGroups(
|
||||||
|
List(newComponentGroup("Data.Table", "first")),
|
||||||
|
List(
|
||||||
|
extendedComponentGroup("Standard", "Base", "Data.Vector", "quux")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
config(
|
||||||
|
"Standard",
|
||||||
|
"Base",
|
||||||
|
ComponentGroups(
|
||||||
|
List(newComponentGroup("Data.Vector", "one", "two")),
|
||||||
|
List(
|
||||||
|
extendedComponentGroup(
|
||||||
|
"Standard",
|
||||||
|
"Table",
|
||||||
|
"Data.Table",
|
||||||
|
"second"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
config(
|
||||||
|
"user",
|
||||||
|
"Vector_Utils",
|
||||||
|
ComponentGroups(
|
||||||
|
List(),
|
||||||
|
List(
|
||||||
|
extendedComponentGroup("Standard", "Base", "Data.Vector", "three")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
resolver.run(testPackages) shouldEqual Vector(
|
||||||
|
libraryComponentGroup(
|
||||||
|
"Standard",
|
||||||
|
"Table",
|
||||||
|
"Data.Table",
|
||||||
|
"first",
|
||||||
|
"second"
|
||||||
|
),
|
||||||
|
libraryComponentGroup(
|
||||||
|
"Standard",
|
||||||
|
"Base",
|
||||||
|
"Data.Vector",
|
||||||
|
"one",
|
||||||
|
"two",
|
||||||
|
"quux",
|
||||||
|
"three"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
"skip component groups extending nothing" in {
|
||||||
|
val resolver = new ComponentGroupsResolver
|
||||||
|
val testPackages = Vector(
|
||||||
|
config(
|
||||||
|
"user",
|
||||||
|
"Unnamed",
|
||||||
|
ComponentGroups(List(newComponentGroup("Main", "main")), List())
|
||||||
|
),
|
||||||
|
config(
|
||||||
|
"Standard",
|
||||||
|
"Base",
|
||||||
|
ComponentGroups(
|
||||||
|
List(newComponentGroup("Data.Vector", "one", "two")),
|
||||||
|
List()
|
||||||
|
)
|
||||||
|
),
|
||||||
|
config(
|
||||||
|
"user",
|
||||||
|
"Vector_Utils",
|
||||||
|
ComponentGroups(
|
||||||
|
List(),
|
||||||
|
List(
|
||||||
|
extendedComponentGroup("Custom", "Lib", "Vector", "three")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
config(
|
||||||
|
"user",
|
||||||
|
"Other_Utils",
|
||||||
|
ComponentGroups(
|
||||||
|
List(),
|
||||||
|
List(
|
||||||
|
extendedComponentGroup("Standard", "Base", "Data.List", "four")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
resolver.run(testPackages) shouldEqual Vector(
|
||||||
|
libraryComponentGroup("user", "Unnamed", "Main", "main"),
|
||||||
|
libraryComponentGroup("Standard", "Base", "Data.Vector", "one", "two")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object ComponentGroupsResolverSpec {
|
||||||
|
|
||||||
|
/** Create a new config. */
|
||||||
|
def config(
|
||||||
|
namespace: String,
|
||||||
|
name: String,
|
||||||
|
componentGroups: ComponentGroups = ComponentGroups.empty
|
||||||
|
): Config =
|
||||||
|
Config(
|
||||||
|
name = name,
|
||||||
|
namespace = namespace,
|
||||||
|
version = "0.0.1",
|
||||||
|
license = "",
|
||||||
|
authors = Nil,
|
||||||
|
maintainers = Nil,
|
||||||
|
edition = None,
|
||||||
|
preferLocalLibraries = true,
|
||||||
|
componentGroups = Right(componentGroups)
|
||||||
|
)
|
||||||
|
|
||||||
|
/** Create a new component group. */
|
||||||
|
def newComponentGroup(
|
||||||
|
module: String,
|
||||||
|
exports: String*
|
||||||
|
): ComponentGroup =
|
||||||
|
ComponentGroup(
|
||||||
|
module = ModuleName(module),
|
||||||
|
color = None,
|
||||||
|
icon = None,
|
||||||
|
exports = exports.map(Component(_, None))
|
||||||
|
)
|
||||||
|
|
||||||
|
/** Create a new extended component group. */
|
||||||
|
def extendedComponentGroup(
|
||||||
|
extendedLibraryNamespace: String,
|
||||||
|
extendedLibraryName: String,
|
||||||
|
extendedModule: String,
|
||||||
|
exports: String*
|
||||||
|
): ExtendedComponentGroup =
|
||||||
|
ExtendedComponentGroup(
|
||||||
|
module = ModuleReference(
|
||||||
|
LibraryName(extendedLibraryNamespace, extendedLibraryName),
|
||||||
|
ModuleName(extendedModule)
|
||||||
|
),
|
||||||
|
exports = exports.map(Component(_, None))
|
||||||
|
)
|
||||||
|
|
||||||
|
/** Create a new library component group. */
|
||||||
|
def libraryComponentGroup(
|
||||||
|
namespace: String,
|
||||||
|
name: String,
|
||||||
|
module: String,
|
||||||
|
exports: String*
|
||||||
|
): LibraryComponentGroup =
|
||||||
|
LibraryComponentGroup(
|
||||||
|
library = LibraryName(namespace, name),
|
||||||
|
module = ModuleName(module),
|
||||||
|
color = None,
|
||||||
|
icon = None,
|
||||||
|
exports = exports.map(Component(_, None))
|
||||||
|
)
|
||||||
|
}
|
@ -0,0 +1,145 @@
|
|||||||
|
package org.enso.languageserver.libraries
|
||||||
|
|
||||||
|
import io.circe.DecodingFailure
|
||||||
|
import org.enso.editions.LibraryName
|
||||||
|
import org.enso.pkg.{ComponentGroups, Config, ModuleName, ModuleReference}
|
||||||
|
import org.scalatest.matchers.should.Matchers
|
||||||
|
import org.scalatest.wordspec.AnyWordSpec
|
||||||
|
|
||||||
|
class ComponentGroupsValidatorSpec extends AnyWordSpec with Matchers {
|
||||||
|
|
||||||
|
import ComponentGroupsValidator._
|
||||||
|
import ComponentGroupsValidatorSpec._
|
||||||
|
import ComponentGroupsResolverSpec._
|
||||||
|
|
||||||
|
"ComponentGroupsValidator" should {
|
||||||
|
|
||||||
|
"validate invalid component groups" in {
|
||||||
|
val validator = new ComponentGroupsValidator
|
||||||
|
val testPackages = Vector(
|
||||||
|
config("Foo", "Bar"),
|
||||||
|
config(
|
||||||
|
"Foo",
|
||||||
|
"Baz",
|
||||||
|
ComponentGroups(List(newComponentGroup("Mod1", "one", "two")), List())
|
||||||
|
),
|
||||||
|
configError(
|
||||||
|
"Foo",
|
||||||
|
"Quux",
|
||||||
|
"Error message"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
validator.validate(testPackages) shouldEqual testPackages.map { config =>
|
||||||
|
config.componentGroups match {
|
||||||
|
case Right(_) =>
|
||||||
|
Right(config)
|
||||||
|
case Left(error) =>
|
||||||
|
Left(
|
||||||
|
ValidationError.InvalidComponentGroups(
|
||||||
|
libraryName(config),
|
||||||
|
error.getMessage()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
"validate duplicate component groups" in {
|
||||||
|
val validator = new ComponentGroupsValidator
|
||||||
|
val testPackages = Vector(
|
||||||
|
config(
|
||||||
|
"Foo",
|
||||||
|
"Bar",
|
||||||
|
ComponentGroups(List(newComponentGroup("Mod1", "a", "b")), List())
|
||||||
|
),
|
||||||
|
config(
|
||||||
|
"Foo",
|
||||||
|
"Bar",
|
||||||
|
ComponentGroups(List(newComponentGroup("Mod1", "one", "two")), List())
|
||||||
|
),
|
||||||
|
config(
|
||||||
|
"Baz",
|
||||||
|
"Quux",
|
||||||
|
ComponentGroups(List(newComponentGroup("Mod1", "one", "two")), List())
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
validator
|
||||||
|
.validate(testPackages) shouldEqual Vector(
|
||||||
|
Right(testPackages(0)),
|
||||||
|
Left(
|
||||||
|
ValidationError.DuplicatedComponentGroup(
|
||||||
|
libraryName(testPackages(1)),
|
||||||
|
ModuleReference(LibraryName("Foo", "Bar"), ModuleName("Mod1"))
|
||||||
|
)
|
||||||
|
),
|
||||||
|
Right(testPackages(2))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
"validate non-existent extensions" in {
|
||||||
|
val validator = new ComponentGroupsValidator
|
||||||
|
val testPackages = Vector(
|
||||||
|
config(
|
||||||
|
"Foo",
|
||||||
|
"Bar",
|
||||||
|
ComponentGroups(List(newComponentGroup("Mod1", "a", "b")), List())
|
||||||
|
),
|
||||||
|
config(
|
||||||
|
"Foo",
|
||||||
|
"Baz",
|
||||||
|
ComponentGroups(
|
||||||
|
List(),
|
||||||
|
List(extendedComponentGroup("Foo", "Bar", "Mod1", "c", "d"))
|
||||||
|
)
|
||||||
|
),
|
||||||
|
config(
|
||||||
|
"Baz",
|
||||||
|
"Quux",
|
||||||
|
ComponentGroups(
|
||||||
|
List(),
|
||||||
|
List(extendedComponentGroup("Foo", "Baz", "Mod1", "quuux"))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
validator
|
||||||
|
.validate(testPackages) shouldEqual Vector(
|
||||||
|
Right(testPackages(0)),
|
||||||
|
Right(testPackages(1)),
|
||||||
|
Left(
|
||||||
|
ValidationError.ComponentGroupExtendsNothing(
|
||||||
|
libraryName(testPackages(2)),
|
||||||
|
ModuleReference(LibraryName("Foo", "Baz"), ModuleName("Mod1"))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object ComponentGroupsValidatorSpec {
|
||||||
|
|
||||||
|
/** Create a new config with containing a component groups error. */
|
||||||
|
def configError(
|
||||||
|
namespace: String,
|
||||||
|
name: String,
|
||||||
|
message: String
|
||||||
|
): Config =
|
||||||
|
Config(
|
||||||
|
name = name,
|
||||||
|
namespace = namespace,
|
||||||
|
version = "0.0.1",
|
||||||
|
license = "",
|
||||||
|
authors = Nil,
|
||||||
|
maintainers = Nil,
|
||||||
|
edition = None,
|
||||||
|
preferLocalLibraries = true,
|
||||||
|
componentGroups = Left(DecodingFailure.apply(message, List()))
|
||||||
|
)
|
||||||
|
|
||||||
|
/** Create a library name from config. */
|
||||||
|
def libraryName(config: Config): LibraryName =
|
||||||
|
LibraryName(config.namespace, config.name)
|
||||||
|
}
|
@ -13,7 +13,19 @@ import org.enso.librarymanager.published.repository.{
|
|||||||
ExampleRepository,
|
ExampleRepository,
|
||||||
LibraryManifest
|
LibraryManifest
|
||||||
}
|
}
|
||||||
import org.enso.pkg.{Contact, PackageManager}
|
import org.enso.pkg.{
|
||||||
|
Component,
|
||||||
|
ComponentGroup,
|
||||||
|
ComponentGroups,
|
||||||
|
Config,
|
||||||
|
Contact,
|
||||||
|
ExtendedComponentGroup,
|
||||||
|
ModuleName,
|
||||||
|
ModuleReference,
|
||||||
|
Package,
|
||||||
|
PackageManager,
|
||||||
|
Shortcut
|
||||||
|
}
|
||||||
import org.enso.yaml.YamlHelper
|
import org.enso.yaml.YamlHelper
|
||||||
|
|
||||||
import java.nio.file.Files
|
import java.nio.file.Files
|
||||||
@ -215,6 +227,96 @@ class LibrariesTest extends BaseServerTest {
|
|||||||
""")
|
""")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
"get the package config" in {
|
||||||
|
val client = getInitialisedWsClient()
|
||||||
|
val testComponentGroups = ComponentGroups(
|
||||||
|
newGroups = List(
|
||||||
|
ComponentGroup(
|
||||||
|
module = ModuleName("Foo"),
|
||||||
|
color = Some("#32a852"),
|
||||||
|
icon = None,
|
||||||
|
exports = Seq(
|
||||||
|
Component("foo", Some(Shortcut("abc"))),
|
||||||
|
Component("bar", None)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
extendedGroups = List(
|
||||||
|
ExtendedComponentGroup(
|
||||||
|
module = ModuleReference(
|
||||||
|
LibraryName("Standard", "Base"),
|
||||||
|
ModuleName("Data")
|
||||||
|
),
|
||||||
|
exports = List(
|
||||||
|
Component("bar", None)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
client.send(json"""
|
||||||
|
{ "jsonrpc": "2.0",
|
||||||
|
"method": "library/create",
|
||||||
|
"id": 0,
|
||||||
|
"params": {
|
||||||
|
"namespace": "user",
|
||||||
|
"name": "Get_Package_Test_Lib",
|
||||||
|
"authors": [],
|
||||||
|
"maintainers": [],
|
||||||
|
"license": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""")
|
||||||
|
client.expectJson(json"""
|
||||||
|
{ "jsonrpc": "2.0",
|
||||||
|
"id": 0,
|
||||||
|
"result": null
|
||||||
|
}
|
||||||
|
""")
|
||||||
|
|
||||||
|
val libraryRoot = getTestDirectory
|
||||||
|
.resolve("test_home")
|
||||||
|
.resolve("libraries")
|
||||||
|
.resolve("user")
|
||||||
|
.resolve("Get_Package_Test_Lib")
|
||||||
|
val packageFile = libraryRoot.resolve(Package.configFileName)
|
||||||
|
val packageConfig =
|
||||||
|
YamlHelper
|
||||||
|
.load[Config](packageFile)
|
||||||
|
.get
|
||||||
|
.copy(
|
||||||
|
componentGroups = Right(testComponentGroups)
|
||||||
|
)
|
||||||
|
Files.writeString(packageFile, packageConfig.toYaml)
|
||||||
|
|
||||||
|
client.send(json"""
|
||||||
|
{ "jsonrpc": "2.0",
|
||||||
|
"method": "library/getPackage",
|
||||||
|
"id": 1,
|
||||||
|
"params": {
|
||||||
|
"namespace": "user",
|
||||||
|
"name": "Get_Package_Test_Lib",
|
||||||
|
"version": {
|
||||||
|
"type": "LocalLibraryVersion"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""")
|
||||||
|
val response = client.expectSomeJson()
|
||||||
|
|
||||||
|
response.hcursor
|
||||||
|
.downField("result")
|
||||||
|
.downField("license")
|
||||||
|
.as[Option[String]]
|
||||||
|
.rightValue shouldEqual None
|
||||||
|
|
||||||
|
response.hcursor
|
||||||
|
.downField("result")
|
||||||
|
.downField("componentGroups")
|
||||||
|
.as[ComponentGroups]
|
||||||
|
.rightValue shouldEqual testComponentGroups
|
||||||
|
}
|
||||||
|
|
||||||
"create, publish a library and fetch its manifest from the server" in {
|
"create, publish a library and fetch its manifest from the server" in {
|
||||||
val client = getInitialisedWsClient()
|
val client = getInitialisedWsClient()
|
||||||
client.send(json"""
|
client.send(json"""
|
||||||
@ -475,14 +577,16 @@ class LibrariesTest extends BaseServerTest {
|
|||||||
.value
|
.value
|
||||||
|
|
||||||
val pkg =
|
val pkg =
|
||||||
PackageManager.Default.loadPackage(cachedLibraryRoot.toFile).get
|
PackageManager.Default
|
||||||
|
.loadPackage(cachedLibraryRoot.location.toFile)
|
||||||
|
.get
|
||||||
pkg.name shouldEqual "Bar"
|
pkg.name shouldEqual "Bar"
|
||||||
pkg.listSources.map(
|
pkg.listSources.map(
|
||||||
_.file.getName
|
_.file.getName
|
||||||
) should contain theSameElementsAs Seq("Main.enso")
|
) should contain theSameElementsAs Seq("Main.enso")
|
||||||
|
|
||||||
assert(
|
assert(
|
||||||
Files.exists(cachedLibraryRoot.resolve(LibraryManifest.filename)),
|
Files.exists(cachedLibraryRoot / LibraryManifest.filename),
|
||||||
"The manifest file of a downloaded library should be saved in the cache too."
|
"The manifest file of a downloaded library should be saved in the cache too."
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -609,6 +713,55 @@ class LibrariesTest extends BaseServerTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
"editions/listDefinedComponents" should {
|
||||||
|
"include expected components in the list" in {
|
||||||
|
val client = getInitialisedWsClient()
|
||||||
|
client.send(json"""
|
||||||
|
{ "jsonrpc": "2.0",
|
||||||
|
"method": "editions/listDefinedComponents",
|
||||||
|
"id": 0,
|
||||||
|
"params": {
|
||||||
|
"edition": {
|
||||||
|
"type": "CurrentProjectEdition"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""")
|
||||||
|
|
||||||
|
client.expectJson(json"""
|
||||||
|
{ "jsonrpc": "2.0",
|
||||||
|
"id": 0,
|
||||||
|
"result": {
|
||||||
|
"availableComponents" : [ ]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""")
|
||||||
|
|
||||||
|
val currentEditionName = buildinfo.Info.currentEdition
|
||||||
|
client.send(json"""
|
||||||
|
{ "jsonrpc": "2.0",
|
||||||
|
"method": "editions/listDefinedComponents",
|
||||||
|
"id": 0,
|
||||||
|
"params": {
|
||||||
|
"edition": {
|
||||||
|
"type": "NamedEdition",
|
||||||
|
"editionName": $currentEditionName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""")
|
||||||
|
|
||||||
|
client.expectJson(json"""
|
||||||
|
{ "jsonrpc": "2.0",
|
||||||
|
"id": 0,
|
||||||
|
"result": {
|
||||||
|
"availableComponents" : [ ]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
"editions/resolve" should {
|
"editions/resolve" should {
|
||||||
"resolve the engine version associated with an edition" in {
|
"resolve the engine version associated with an edition" in {
|
||||||
val currentVersion = buildinfo.Info.ensoVersion
|
val currentVersion = buildinfo.Info.ensoVersion
|
||||||
|
@ -10,6 +10,7 @@ import org.enso.interpreter.instrument.NotificationHandler
|
|||||||
import org.enso.interpreter.runtime.builtin.Builtins
|
import org.enso.interpreter.runtime.builtin.Builtins
|
||||||
import org.enso.interpreter.runtime.util.TruffleFileSystem
|
import org.enso.interpreter.runtime.util.TruffleFileSystem
|
||||||
import org.enso.interpreter.runtime.{Context, Module}
|
import org.enso.interpreter.runtime.{Context, Module}
|
||||||
|
import org.enso.librarymanager.resolved.LibraryRoot
|
||||||
import org.enso.librarymanager.{
|
import org.enso.librarymanager.{
|
||||||
DefaultLibraryProvider,
|
DefaultLibraryProvider,
|
||||||
ResolvingLibraryProvider
|
ResolvingLibraryProvider
|
||||||
@ -23,6 +24,7 @@ import org.enso.pkg.{
|
|||||||
PackageManager,
|
PackageManager,
|
||||||
QualifiedName
|
QualifiedName
|
||||||
}
|
}
|
||||||
|
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
|
|
||||||
import scala.util.Try
|
import scala.util.Try
|
||||||
@ -247,14 +249,14 @@ object PackageRepository {
|
|||||||
private def loadPackage(
|
private def loadPackage(
|
||||||
libraryName: LibraryName,
|
libraryName: LibraryName,
|
||||||
libraryVersion: LibraryVersion,
|
libraryVersion: LibraryVersion,
|
||||||
root: Path
|
root: LibraryRoot
|
||||||
): Either[Error, Package[TruffleFile]] = Try {
|
): Either[Error, Package[TruffleFile]] = Try {
|
||||||
logger.debug(
|
logger.debug(
|
||||||
s"Loading library $libraryName from " +
|
s"Loading library $libraryName from " +
|
||||||
s"[${MaskedPath(root).applyMasking()}]."
|
s"[${MaskedPath(root.location).applyMasking()}]."
|
||||||
)
|
)
|
||||||
val rootFile = context.getEnvironment.getInternalTruffleFile(
|
val rootFile = context.getEnvironment.getInternalTruffleFile(
|
||||||
root.toAbsolutePath.normalize.toString
|
root.location.toAbsolutePath.normalize.toString
|
||||||
)
|
)
|
||||||
val pkg = packageManager.loadPackage(rootFile).get
|
val pkg = packageManager.loadPackage(rootFile).get
|
||||||
registerPackageInternal(
|
registerPackageInternal(
|
||||||
@ -362,7 +364,7 @@ object PackageRepository {
|
|||||||
case Right(resolved) =>
|
case Right(resolved) =>
|
||||||
logger.info(
|
logger.info(
|
||||||
s"Found library ${resolved.name} @ ${resolved.version} " +
|
s"Found library ${resolved.name} @ ${resolved.version} " +
|
||||||
s"at [${MaskedPath(resolved.location).applyMasking()}]."
|
s"at [${MaskedPath(resolved.root.location).applyMasking()}]."
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -373,7 +375,7 @@ object PackageRepository {
|
|||||||
else
|
else
|
||||||
resolvedLibrary
|
resolvedLibrary
|
||||||
.flatMap { library =>
|
.flatMap { library =>
|
||||||
loadPackage(library.name, library.version, library.location)
|
loadPackage(library.name, library.version, library.root)
|
||||||
}
|
}
|
||||||
.flatMap(resolveComponentGroups)
|
.flatMap(resolveComponentGroups)
|
||||||
.left
|
.left
|
||||||
|
@ -62,8 +62,6 @@ class RuntimeComponentsTest
|
|||||||
LibraryName("Standard", "Base"),
|
LibraryName("Standard", "Base"),
|
||||||
ModuleName("Group2")
|
ModuleName("Group2")
|
||||||
),
|
),
|
||||||
color = None,
|
|
||||||
icon = None,
|
|
||||||
exports = List(Component("foo", None))
|
exports = List(Component("foo", None))
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@ -41,13 +41,14 @@ class LibraryDownloadTest
|
|||||||
)
|
)
|
||||||
.get
|
.get
|
||||||
}
|
}
|
||||||
val pkg = PackageManager.Default.loadPackage(libPath.toFile).get
|
val pkg =
|
||||||
|
PackageManager.Default.loadPackage(libPath.location.toFile).get
|
||||||
pkg.name shouldEqual "Bar"
|
pkg.name shouldEqual "Bar"
|
||||||
val sources = pkg.listSources
|
val sources = pkg.listSources
|
||||||
sources should have size 1
|
sources should have size 1
|
||||||
sources.head.file.getName shouldEqual "Main.enso"
|
sources.head.file.getName shouldEqual "Main.enso"
|
||||||
assert(
|
assert(
|
||||||
Files.notExists(libPath.resolve("LICENSE.md")),
|
Files.notExists(libPath / "LICENSE.md"),
|
||||||
"The license file should not exist as it was not provided " +
|
"The license file should not exist as it was not provided " +
|
||||||
"in the repository."
|
"in the repository."
|
||||||
)
|
)
|
||||||
|
@ -80,7 +80,9 @@ class LibraryUploadTest
|
|||||||
)
|
)
|
||||||
val installedRoot =
|
val installedRoot =
|
||||||
cache.findOrInstallLibrary(libraryName, libraryVersion, repo).get
|
cache.findOrInstallLibrary(libraryName, libraryVersion, repo).get
|
||||||
val pkg = PackageManager.Default.loadPackage(installedRoot.toFile).get
|
val pkg = PackageManager.Default
|
||||||
|
.loadPackage(installedRoot.location.toFile)
|
||||||
|
.get
|
||||||
pkg.name shouldEqual libraryName.name
|
pkg.name shouldEqual libraryName.name
|
||||||
val sources = pkg.listSources
|
val sources = pkg.listSources
|
||||||
sources should have size 1
|
sources should have size 1
|
||||||
|
@ -1,12 +1,32 @@
|
|||||||
package org.enso.librarymanager
|
package org.enso.librarymanager
|
||||||
|
|
||||||
import org.enso.editions.{LibraryName, LibraryVersion}
|
import org.enso.editions.{LibraryName, LibraryVersion}
|
||||||
|
import org.enso.librarymanager.resolved.{
|
||||||
|
FilesystemLibraryReadAccess,
|
||||||
|
LibraryReadAccess,
|
||||||
|
LibraryRoot
|
||||||
|
}
|
||||||
|
|
||||||
import java.nio.file.Path
|
/** Represents a resolved library that is located somewhere on the filesystem.
|
||||||
|
*
|
||||||
/** Represents a resolved library that is located somewhere on the filesystem. */
|
* @param name the library name
|
||||||
|
* @param version the library version
|
||||||
|
* @param root the library location on the filesystem
|
||||||
|
*/
|
||||||
case class ResolvedLibrary(
|
case class ResolvedLibrary(
|
||||||
name: LibraryName,
|
name: LibraryName,
|
||||||
version: LibraryVersion,
|
version: LibraryVersion,
|
||||||
location: Path
|
root: LibraryRoot
|
||||||
)
|
)
|
||||||
|
object ResolvedLibrary {
|
||||||
|
|
||||||
|
/** Extension methods of [[ResolvedLibrary]]. */
|
||||||
|
implicit class ResolvedLibraryMethods(val resolvedLibrary: ResolvedLibrary)
|
||||||
|
extends AnyVal {
|
||||||
|
|
||||||
|
/** Provides read methods to access the library files. */
|
||||||
|
def getReadAccess: LibraryReadAccess =
|
||||||
|
new FilesystemLibraryReadAccess(resolvedLibrary.root)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
@ -8,10 +8,8 @@ import org.enso.librarymanager.published.repository.LibraryManifest
|
|||||||
import org.enso.librarymanager.published.repository.RepositoryHelper.RepositoryMethods
|
import org.enso.librarymanager.published.repository.RepositoryHelper.RepositoryMethods
|
||||||
import org.enso.libraryupload.DependencyExtractor
|
import org.enso.libraryupload.DependencyExtractor
|
||||||
import org.enso.pkg.PackageManager
|
import org.enso.pkg.PackageManager
|
||||||
import org.enso.yaml.YamlHelper
|
|
||||||
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.nio.file.Files
|
|
||||||
import scala.util.Try
|
import scala.util.Try
|
||||||
|
|
||||||
/** A helper class that allows to find all transitive dependencies of a specific
|
/** A helper class that allows to find all transitive dependencies of a specific
|
||||||
@ -61,7 +59,7 @@ class DependencyResolver(
|
|||||||
case LibraryVersion.Local =>
|
case LibraryVersion.Local =>
|
||||||
val libraryPath = localLibraryProvider.findLibrary(libraryName)
|
val libraryPath = localLibraryProvider.findLibrary(libraryName)
|
||||||
val libraryPackage = libraryPath.map(path =>
|
val libraryPackage = libraryPath.map(path =>
|
||||||
PackageManager.Default.loadPackage(path.toFile).get
|
PackageManager.Default.loadPackage(path.location.toFile).get
|
||||||
)
|
)
|
||||||
|
|
||||||
val dependencies = libraryPackage match {
|
val dependencies = libraryPackage match {
|
||||||
@ -99,16 +97,11 @@ class DependencyResolver(
|
|||||||
): LibraryManifest = {
|
): LibraryManifest = {
|
||||||
val cachedManifest = publishedLibraryProvider
|
val cachedManifest = publishedLibraryProvider
|
||||||
.findCachedLibrary(libraryName, version.version)
|
.findCachedLibrary(libraryName, version.version)
|
||||||
.flatMap { libraryPath =>
|
.flatMap(_.getReadAccess.readManifest().flatMap(_.toOption))
|
||||||
val manifestPath = libraryPath.resolve(LibraryManifest.filename)
|
|
||||||
if (Files.exists(manifestPath))
|
|
||||||
YamlHelper.load[LibraryManifest](manifestPath).toOption
|
|
||||||
else None
|
|
||||||
}
|
|
||||||
cachedManifest.getOrElse {
|
cachedManifest.getOrElse {
|
||||||
version.repository
|
version.repository
|
||||||
.accessLibrary(libraryName, version.version)
|
.accessLibrary(libraryName, version.version)
|
||||||
.downloadManifest()
|
.fetchManifest()
|
||||||
.force()
|
.force()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,9 +3,11 @@ package org.enso.librarymanager.local
|
|||||||
import com.typesafe.scalalogging.Logger
|
import com.typesafe.scalalogging.Logger
|
||||||
import org.enso.editions.LibraryName
|
import org.enso.editions.LibraryName
|
||||||
import org.enso.librarymanager.LibraryLocations
|
import org.enso.librarymanager.LibraryLocations
|
||||||
|
import org.enso.librarymanager.resolved.LibraryRoot
|
||||||
import org.enso.logger.masking.MaskedPath
|
import org.enso.logger.masking.MaskedPath
|
||||||
|
|
||||||
import java.nio.file.{Files, Path}
|
import java.nio.file.{Files, Path}
|
||||||
|
|
||||||
import scala.annotation.tailrec
|
import scala.annotation.tailrec
|
||||||
|
|
||||||
/** A default implementation of [[LocalLibraryProvider]]. */
|
/** A default implementation of [[LocalLibraryProvider]]. */
|
||||||
@ -15,13 +17,13 @@ class DefaultLocalLibraryProvider(searchPaths: List[Path])
|
|||||||
private val logger = Logger[DefaultLocalLibraryProvider]
|
private val logger = Logger[DefaultLocalLibraryProvider]
|
||||||
|
|
||||||
/** @inheritdoc */
|
/** @inheritdoc */
|
||||||
override def findLibrary(libraryName: LibraryName): Option[Path] =
|
override def findLibrary(libraryName: LibraryName): Option[LibraryRoot] = {
|
||||||
findLibraryHelper(
|
findLibraryHelper(libraryName, searchPaths)
|
||||||
libraryName,
|
.map(LibraryRoot(_))
|
||||||
searchPaths
|
}
|
||||||
)
|
|
||||||
|
|
||||||
/** Searches through the available library paths, checking if any one of them contains the requested library.
|
/** Searches through the available library paths, checking if any one of them
|
||||||
|
* contains the requested library.
|
||||||
*
|
*
|
||||||
* The first path on the list takes precedence.
|
* The first path on the list takes precedence.
|
||||||
*/
|
*/
|
||||||
|
@ -2,16 +2,19 @@ package org.enso.librarymanager.local
|
|||||||
|
|
||||||
import org.enso.distribution.FileSystem.PathSyntax
|
import org.enso.distribution.FileSystem.PathSyntax
|
||||||
import org.enso.editions.LibraryName
|
import org.enso.editions.LibraryName
|
||||||
|
import org.enso.librarymanager.resolved.LibraryRoot
|
||||||
|
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
|
|
||||||
/** A provider for local libraries. */
|
/** A provider for local libraries. */
|
||||||
trait LocalLibraryProvider {
|
trait LocalLibraryProvider {
|
||||||
|
|
||||||
/** Returns the path to a local instance of the requested library, if it is
|
/** Find the local library by name.
|
||||||
* available.
|
*
|
||||||
|
* @param libraryName the library name
|
||||||
|
* @return the location of the requested library, if it is available.
|
||||||
*/
|
*/
|
||||||
def findLibrary(libraryName: LibraryName): Option[Path]
|
def findLibrary(libraryName: LibraryName): Option[LibraryRoot]
|
||||||
}
|
}
|
||||||
|
|
||||||
object LocalLibraryProvider {
|
object LocalLibraryProvider {
|
||||||
|
@ -1,27 +1,24 @@
|
|||||||
package org.enso.librarymanager.published
|
package org.enso.librarymanager.published
|
||||||
|
|
||||||
import nl.gn0s1s.bump.SemVer
|
import nl.gn0s1s.bump.SemVer
|
||||||
import org.enso.editions.{Editions, LibraryName}
|
import org.enso.editions.LibraryName
|
||||||
import org.enso.librarymanager.LibraryResolutionError
|
|
||||||
import org.enso.librarymanager.published.cache.ReadOnlyLibraryCache
|
import org.enso.librarymanager.published.cache.ReadOnlyLibraryCache
|
||||||
|
import org.enso.librarymanager.resolved.LibraryRoot
|
||||||
|
|
||||||
import java.nio.file.Path
|
|
||||||
import scala.annotation.tailrec
|
import scala.annotation.tailrec
|
||||||
import scala.util.Try
|
|
||||||
|
|
||||||
/** A [[PublishedLibraryProvider]] that just provides libraries which are
|
/** A [[PublishedLibraryCache]] that just provides libraries which are
|
||||||
* already available in the cache.
|
* already available in the cache.
|
||||||
*/
|
*/
|
||||||
class CachedLibraryProvider(caches: List[ReadOnlyLibraryCache])
|
class CachedLibraryProvider(caches: List[ReadOnlyLibraryCache])
|
||||||
extends PublishedLibraryProvider
|
extends PublishedLibraryCache {
|
||||||
with PublishedLibraryCache {
|
|
||||||
|
|
||||||
@tailrec
|
@tailrec
|
||||||
private def findCachedHelper(
|
private def findCachedHelper(
|
||||||
libraryName: LibraryName,
|
libraryName: LibraryName,
|
||||||
version: SemVer,
|
version: SemVer,
|
||||||
caches: List[ReadOnlyLibraryCache]
|
caches: List[ReadOnlyLibraryCache]
|
||||||
): Option[Path] = caches match {
|
): Option[LibraryRoot] = caches match {
|
||||||
case head :: tail =>
|
case head :: tail =>
|
||||||
head.findCachedLibrary(libraryName, version) match {
|
head.findCachedLibrary(libraryName, version) match {
|
||||||
case Some(found) => Some(found)
|
case Some(found) => Some(found)
|
||||||
@ -34,21 +31,8 @@ class CachedLibraryProvider(caches: List[ReadOnlyLibraryCache])
|
|||||||
override def findCachedLibrary(
|
override def findCachedLibrary(
|
||||||
libraryName: LibraryName,
|
libraryName: LibraryName,
|
||||||
version: SemVer
|
version: SemVer
|
||||||
): Option[Path] = findCachedHelper(libraryName, version, caches)
|
): Option[LibraryRoot] =
|
||||||
|
findCachedHelper(libraryName, version, caches)
|
||||||
/** @inheritdoc */
|
|
||||||
override def findLibrary(
|
|
||||||
libraryName: LibraryName,
|
|
||||||
version: SemVer,
|
|
||||||
recommendedRepository: Editions.Repository
|
|
||||||
): Try[Path] =
|
|
||||||
findCachedLibrary(libraryName, version)
|
|
||||||
.toRight(
|
|
||||||
LibraryResolutionError(
|
|
||||||
s"Library [$libraryName:$version] was not found in the cache."
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.toTry
|
|
||||||
|
|
||||||
/** @inheritdoc */
|
/** @inheritdoc */
|
||||||
override def isLibraryCached(
|
override def isLibraryCached(
|
||||||
|
@ -7,8 +7,8 @@ import org.enso.librarymanager.published.cache.{
|
|||||||
LibraryCache,
|
LibraryCache,
|
||||||
ReadOnlyLibraryCache
|
ReadOnlyLibraryCache
|
||||||
}
|
}
|
||||||
|
import org.enso.librarymanager.resolved.LibraryRoot
|
||||||
|
|
||||||
import java.nio.file.Path
|
|
||||||
import scala.util.{Success, Try}
|
import scala.util.{Success, Try}
|
||||||
|
|
||||||
/** A default implementation of [[PublishedLibraryProvider]] which uses one
|
/** A default implementation of [[PublishedLibraryProvider]] which uses one
|
||||||
@ -18,7 +18,8 @@ import scala.util.{Success, Try}
|
|||||||
class DefaultPublishedLibraryProvider(
|
class DefaultPublishedLibraryProvider(
|
||||||
primaryCache: LibraryCache,
|
primaryCache: LibraryCache,
|
||||||
auxiliaryCaches: List[ReadOnlyLibraryCache]
|
auxiliaryCaches: List[ReadOnlyLibraryCache]
|
||||||
) extends CachedLibraryProvider(caches = primaryCache :: auxiliaryCaches) {
|
) extends CachedLibraryProvider(caches = primaryCache :: auxiliaryCaches)
|
||||||
|
with PublishedLibraryProvider {
|
||||||
private val logger = Logger[DefaultPublishedLibraryProvider]
|
private val logger = Logger[DefaultPublishedLibraryProvider]
|
||||||
|
|
||||||
/** @inheritdoc */
|
/** @inheritdoc */
|
||||||
@ -26,9 +27,9 @@ class DefaultPublishedLibraryProvider(
|
|||||||
libraryName: LibraryName,
|
libraryName: LibraryName,
|
||||||
version: SemVer,
|
version: SemVer,
|
||||||
recommendedRepository: Editions.Repository
|
recommendedRepository: Editions.Repository
|
||||||
): Try[Path] = {
|
): Try[LibraryRoot] = {
|
||||||
val cached = findCachedLibrary(libraryName, version)
|
val cachedLibrary = findCachedLibrary(libraryName, version)
|
||||||
cached.map(Success(_)).getOrElse {
|
cachedLibrary.map(Success(_)).getOrElse {
|
||||||
logger.trace(
|
logger.trace(
|
||||||
s"$libraryName was not found in any caches, it will need to be " +
|
s"$libraryName was not found in any caches, it will need to be " +
|
||||||
s"downloaded."
|
s"downloaded."
|
||||||
|
@ -4,6 +4,7 @@ import nl.gn0s1s.bump.SemVer
|
|||||||
import org.enso.editions.LibraryName
|
import org.enso.editions.LibraryName
|
||||||
import org.enso.librarymanager.LibraryLocations
|
import org.enso.librarymanager.LibraryLocations
|
||||||
import org.enso.librarymanager.published.bundles.LocalReadOnlyRepository
|
import org.enso.librarymanager.published.bundles.LocalReadOnlyRepository
|
||||||
|
import org.enso.librarymanager.resolved.LibraryRoot
|
||||||
|
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
|
|
||||||
@ -18,7 +19,10 @@ trait PublishedLibraryCache {
|
|||||||
def isLibraryCached(libraryName: LibraryName, version: SemVer): Boolean
|
def isLibraryCached(libraryName: LibraryName, version: SemVer): Boolean
|
||||||
|
|
||||||
/** Tries to locate a cached version of the requested library. */
|
/** Tries to locate a cached version of the requested library. */
|
||||||
def findCachedLibrary(libraryName: LibraryName, version: SemVer): Option[Path]
|
def findCachedLibrary(
|
||||||
|
libraryName: LibraryName,
|
||||||
|
version: SemVer
|
||||||
|
): Option[LibraryRoot]
|
||||||
}
|
}
|
||||||
|
|
||||||
object PublishedLibraryCache {
|
object PublishedLibraryCache {
|
||||||
|
@ -3,8 +3,8 @@ package org.enso.librarymanager.published
|
|||||||
import nl.gn0s1s.bump.SemVer
|
import nl.gn0s1s.bump.SemVer
|
||||||
import org.enso.editions.Editions.Repository
|
import org.enso.editions.Editions.Repository
|
||||||
import org.enso.editions.LibraryName
|
import org.enso.editions.LibraryName
|
||||||
|
import org.enso.librarymanager.resolved.LibraryRoot
|
||||||
|
|
||||||
import java.nio.file.Path
|
|
||||||
import scala.util.Try
|
import scala.util.Try
|
||||||
|
|
||||||
/** A provider of published libraries.
|
/** A provider of published libraries.
|
||||||
@ -24,5 +24,5 @@ trait PublishedLibraryProvider {
|
|||||||
libraryName: LibraryName,
|
libraryName: LibraryName,
|
||||||
version: SemVer,
|
version: SemVer,
|
||||||
recommendedRepository: Repository
|
recommendedRepository: Repository
|
||||||
): Try[Path]
|
): Try[LibraryRoot]
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ import org.enso.librarymanager.published.cache.{
|
|||||||
LibraryCache,
|
LibraryCache,
|
||||||
ReadOnlyLibraryCache
|
ReadOnlyLibraryCache
|
||||||
}
|
}
|
||||||
|
import org.enso.librarymanager.resolved.LibraryRoot
|
||||||
import org.enso.logger.masking.MaskedPath
|
import org.enso.logger.masking.MaskedPath
|
||||||
|
|
||||||
import java.nio.file.{Files, Path}
|
import java.nio.file.{Files, Path}
|
||||||
@ -31,13 +32,13 @@ class LocalReadOnlyRepository(root: Path) extends ReadOnlyLibraryCache {
|
|||||||
override def findCachedLibrary(
|
override def findCachedLibrary(
|
||||||
libraryName: LibraryName,
|
libraryName: LibraryName,
|
||||||
version: SemVer
|
version: SemVer
|
||||||
): Option[Path] = {
|
): Option[LibraryRoot] = {
|
||||||
val path = LibraryCache.resolvePath(root, libraryName, version)
|
val path = LibraryCache.resolvePath(root, libraryName, version)
|
||||||
if (Files.exists(path)) {
|
if (Files.exists(path)) {
|
||||||
logger.trace(
|
logger.trace(
|
||||||
s"$libraryName found at [${MaskedPath(path).applyMasking()}]."
|
s"$libraryName found at [${MaskedPath(path).applyMasking()}]."
|
||||||
)
|
)
|
||||||
Some(path)
|
Some(LibraryRoot(path))
|
||||||
} else {
|
} else {
|
||||||
logger.trace(
|
logger.trace(
|
||||||
s"Did not find $libraryName at [${MaskedPath(path).applyMasking()}]."
|
s"Did not find $libraryName at [${MaskedPath(path).applyMasking()}]."
|
||||||
|
@ -18,11 +18,13 @@ import org.enso.librarymanager.published.repository.RepositoryHelper.{
|
|||||||
LibraryAccess,
|
LibraryAccess,
|
||||||
RepositoryMethods
|
RepositoryMethods
|
||||||
}
|
}
|
||||||
|
import org.enso.librarymanager.resolved.LibraryRoot
|
||||||
import org.enso.logger.masking.MaskedPath
|
import org.enso.logger.masking.MaskedPath
|
||||||
import org.enso.pkg.PackageManager
|
import org.enso.pkg.PackageManager
|
||||||
import org.enso.yaml.YamlHelper
|
import org.enso.yaml.YamlHelper
|
||||||
|
|
||||||
import java.nio.file.{Files, Path}
|
import java.nio.file.{Files, Path}
|
||||||
|
|
||||||
import scala.util.control.NonFatal
|
import scala.util.control.NonFatal
|
||||||
import scala.util.{Success, Try}
|
import scala.util.{Success, Try}
|
||||||
|
|
||||||
@ -50,7 +52,7 @@ class DownloadingLibraryCache(
|
|||||||
override def findCachedLibrary(
|
override def findCachedLibrary(
|
||||||
libraryName: LibraryName,
|
libraryName: LibraryName,
|
||||||
version: SemVer
|
version: SemVer
|
||||||
): Option[Path] = {
|
): Option[LibraryRoot] = {
|
||||||
val path = LibraryCache.resolvePath(cacheRoot, libraryName, version)
|
val path = LibraryCache.resolvePath(cacheRoot, libraryName, version)
|
||||||
resourceManager.withResource(
|
resourceManager.withResource(
|
||||||
lockUserInterface,
|
lockUserInterface,
|
||||||
@ -62,7 +64,7 @@ class DownloadingLibraryCache(
|
|||||||
s"Library [$libraryName:$version] found cached at " +
|
s"Library [$libraryName:$version] found cached at " +
|
||||||
s"[${MaskedPath(path).applyMasking()}]."
|
s"[${MaskedPath(path).applyMasking()}]."
|
||||||
)
|
)
|
||||||
Some(path)
|
Some(LibraryRoot(path))
|
||||||
} else None
|
} else None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -72,7 +74,7 @@ class DownloadingLibraryCache(
|
|||||||
libraryName: LibraryName,
|
libraryName: LibraryName,
|
||||||
version: SemVer,
|
version: SemVer,
|
||||||
recommendedRepository: Editions.Repository
|
recommendedRepository: Editions.Repository
|
||||||
): Try[Path] = {
|
): Try[LibraryRoot] = {
|
||||||
val cached = findCachedLibrary(libraryName, version)
|
val cached = findCachedLibrary(libraryName, version)
|
||||||
cached match {
|
cached match {
|
||||||
case Some(result) => Success(result)
|
case Some(result) => Success(result)
|
||||||
@ -95,7 +97,7 @@ class DownloadingLibraryCache(
|
|||||||
libraryName: LibraryName,
|
libraryName: LibraryName,
|
||||||
version: SemVer,
|
version: SemVer,
|
||||||
recommendedRepository: Editions.Repository
|
recommendedRepository: Editions.Repository
|
||||||
): Try[Path] = Try {
|
): Try[LibraryRoot] = Try {
|
||||||
logger.trace(s"Trying to install [$libraryName:$version].")
|
logger.trace(s"Trying to install [$libraryName:$version].")
|
||||||
resourceManager.withResource(
|
resourceManager.withResource(
|
||||||
lockUserInterface,
|
lockUserInterface,
|
||||||
@ -108,7 +110,7 @@ class DownloadingLibraryCache(
|
|||||||
logger.info(
|
logger.info(
|
||||||
s"Another process has just installed [$libraryName:$version]."
|
s"Another process has just installed [$libraryName:$version]."
|
||||||
)
|
)
|
||||||
cachedLibraryPath
|
LibraryRoot(cachedLibraryPath)
|
||||||
} else {
|
} else {
|
||||||
val access = recommendedRepository.accessLibrary(libraryName, version)
|
val access = recommendedRepository.accessLibrary(libraryName, version)
|
||||||
val manifest = downloadManifest(libraryName, access)
|
val manifest = downloadManifest(libraryName, access)
|
||||||
@ -129,7 +131,7 @@ class DownloadingLibraryCache(
|
|||||||
destination = cachedLibraryPath
|
destination = cachedLibraryPath
|
||||||
)
|
)
|
||||||
|
|
||||||
cachedLibraryPath
|
LibraryRoot(cachedLibraryPath)
|
||||||
} catch {
|
} catch {
|
||||||
case NonFatal(exception) =>
|
case NonFatal(exception) =>
|
||||||
logger.error(
|
logger.error(
|
||||||
@ -149,7 +151,7 @@ class DownloadingLibraryCache(
|
|||||||
libraryName: LibraryName,
|
libraryName: LibraryName,
|
||||||
access: LibraryAccess
|
access: LibraryAccess
|
||||||
): LibraryManifest = {
|
): LibraryManifest = {
|
||||||
val manifestDownload = access.downloadManifest()
|
val manifestDownload = access.fetchManifest()
|
||||||
progressReporter.trackProgress(
|
progressReporter.trackProgress(
|
||||||
s"Downloading library manifest of [$libraryName].",
|
s"Downloading library manifest of [$libraryName].",
|
||||||
manifestDownload
|
manifestDownload
|
||||||
|
@ -2,8 +2,10 @@ package org.enso.librarymanager.published.cache
|
|||||||
|
|
||||||
import nl.gn0s1s.bump.SemVer
|
import nl.gn0s1s.bump.SemVer
|
||||||
import org.enso.editions.{Editions, LibraryName, LibraryVersion}
|
import org.enso.editions.{Editions, LibraryName, LibraryVersion}
|
||||||
|
import org.enso.librarymanager.resolved.LibraryRoot
|
||||||
|
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
|
|
||||||
import scala.util.Try
|
import scala.util.Try
|
||||||
|
|
||||||
/** A library cache that is also capable of downloading missing libraries (which
|
/** A library cache that is also capable of downloading missing libraries (which
|
||||||
@ -26,7 +28,7 @@ trait LibraryCache extends ReadOnlyLibraryCache {
|
|||||||
override def findCachedLibrary(
|
override def findCachedLibrary(
|
||||||
libraryName: LibraryName,
|
libraryName: LibraryName,
|
||||||
version: SemVer
|
version: SemVer
|
||||||
): Option[Path]
|
): Option[LibraryRoot]
|
||||||
|
|
||||||
/** If the cache contains the library, it is returned immediately, otherwise,
|
/** If the cache contains the library, it is returned immediately, otherwise,
|
||||||
* it tries to download the missing library.
|
* it tries to download the missing library.
|
||||||
@ -46,7 +48,7 @@ trait LibraryCache extends ReadOnlyLibraryCache {
|
|||||||
libraryName: LibraryName,
|
libraryName: LibraryName,
|
||||||
version: SemVer,
|
version: SemVer,
|
||||||
recommendedRepository: Editions.Repository
|
recommendedRepository: Editions.Repository
|
||||||
): Try[Path]
|
): Try[LibraryRoot]
|
||||||
|
|
||||||
/** Ensures that the given library and all of its dependencies are installed.
|
/** Ensures that the given library and all of its dependencies are installed.
|
||||||
*
|
*
|
||||||
|
@ -2,8 +2,7 @@ package org.enso.librarymanager.published.cache
|
|||||||
|
|
||||||
import nl.gn0s1s.bump.SemVer
|
import nl.gn0s1s.bump.SemVer
|
||||||
import org.enso.editions.LibraryName
|
import org.enso.editions.LibraryName
|
||||||
|
import org.enso.librarymanager.resolved.LibraryRoot
|
||||||
import java.nio.file.Path
|
|
||||||
|
|
||||||
/** A read-only cache may contain some pre-defined set of libraries, but it does
|
/** A read-only cache may contain some pre-defined set of libraries, but it does
|
||||||
* not necessarily install any additional libraries.
|
* not necessarily install any additional libraries.
|
||||||
@ -15,5 +14,8 @@ trait ReadOnlyLibraryCache {
|
|||||||
/** Locates the library in the cache and returns the path to its root if it
|
/** Locates the library in the cache and returns the path to its root if it
|
||||||
* has been found.
|
* has been found.
|
||||||
*/
|
*/
|
||||||
def findCachedLibrary(libraryName: LibraryName, version: SemVer): Option[Path]
|
def findCachedLibrary(
|
||||||
|
libraryName: LibraryName,
|
||||||
|
version: SemVer
|
||||||
|
): Option[LibraryRoot]
|
||||||
}
|
}
|
||||||
|
@ -6,10 +6,10 @@ import org.enso.distribution.FileSystem.PathSyntax
|
|||||||
import org.enso.downloader.http.{HTTPDownload, URIBuilder}
|
import org.enso.downloader.http.{HTTPDownload, URIBuilder}
|
||||||
import org.enso.editions.Editions.Repository
|
import org.enso.editions.Editions.Repository
|
||||||
import org.enso.editions.LibraryName
|
import org.enso.editions.LibraryName
|
||||||
import org.enso.pkg.Package
|
import org.enso.pkg.{Config, Package}
|
||||||
import org.enso.yaml.YamlHelper
|
import org.enso.yaml.YamlHelper
|
||||||
|
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
|
|
||||||
import scala.util.Failure
|
import scala.util.Failure
|
||||||
|
|
||||||
/** A class that manages the HTTP API of the Library Repository.
|
/** A class that manages the HTTP API of the Library Repository.
|
||||||
@ -52,15 +52,15 @@ object RepositoryHelper {
|
|||||||
libraryRoot: URIBuilder
|
libraryRoot: URIBuilder
|
||||||
) {
|
) {
|
||||||
|
|
||||||
/** Downloads and parses the manifest file.
|
/** Fetches the contents of manifest file and parses it.
|
||||||
*
|
*
|
||||||
* If the repository responds with 404 status code, it returns a special
|
* If the repository responds with 404 status code, it returns a special
|
||||||
* [[LibraryNotFoundException]] indicating that the repository does not
|
* [[LibraryNotFoundException]] indicating that the repository does not
|
||||||
* provide that library. Any other failures are indicated with the more
|
* provide that library. Any other failures are indicated with the more
|
||||||
* generic [[LibraryDownloadFailure]].
|
* generic [[LibraryDownloadFailure]].
|
||||||
*/
|
*/
|
||||||
def downloadManifest(): TaskProgress[LibraryManifest] = {
|
def fetchManifest(): TaskProgress[LibraryManifest] = {
|
||||||
val url = (libraryRoot / manifestFilename).build()
|
val url = (libraryRoot / LibraryManifest.filename).build()
|
||||||
HTTPDownload.fetchString(url).flatMap { response =>
|
HTTPDownload.fetchString(url).flatMap { response =>
|
||||||
response.statusCode match {
|
response.statusCode match {
|
||||||
case 200 =>
|
case 200 =>
|
||||||
@ -80,6 +80,34 @@ object RepositoryHelper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Fetches the contents of package config file and parses it.
|
||||||
|
*
|
||||||
|
* If the repository responds with 404 status code, it returns a special
|
||||||
|
* [[LibraryNotFoundException]] indicating that the repository does not
|
||||||
|
* provide that library. Any other failures are indicated with the more
|
||||||
|
* generic [[LibraryDownloadFailure]].
|
||||||
|
*/
|
||||||
|
def fetchPackageConfig(): TaskProgress[Config] = {
|
||||||
|
val url = (libraryRoot / Package.configFileName).build()
|
||||||
|
HTTPDownload.fetchString(url).flatMap { response =>
|
||||||
|
response.statusCode match {
|
||||||
|
case 200 =>
|
||||||
|
YamlHelper.parseString[Config](response.content).toTry
|
||||||
|
case 404 =>
|
||||||
|
Failure(
|
||||||
|
LibraryNotFoundException(libraryName, version, url.toString)
|
||||||
|
)
|
||||||
|
case code =>
|
||||||
|
Failure(
|
||||||
|
new LibraryDownloadFailure(
|
||||||
|
s"Could not download the package config: The repository responded " +
|
||||||
|
s"with $code status code."
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** A helper that downloads an artifact to a specific location. */
|
/** A helper that downloads an artifact to a specific location. */
|
||||||
private def downloadArtifact(
|
private def downloadArtifact(
|
||||||
artifactName: String,
|
artifactName: String,
|
||||||
@ -108,9 +136,6 @@ object RepositoryHelper {
|
|||||||
): TaskProgress[Unit] = downloadArtifact(archiveName, destinationDirectory)
|
): TaskProgress[Unit] = downloadArtifact(archiveName, destinationDirectory)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Name of the manifest file. */
|
|
||||||
val manifestFilename = "manifest.yaml"
|
|
||||||
|
|
||||||
/** Name of the attached license file. */
|
/** Name of the attached license file. */
|
||||||
val licenseFilename = "LICENSE.md"
|
val licenseFilename = "LICENSE.md"
|
||||||
|
|
||||||
|
@ -0,0 +1,32 @@
|
|||||||
|
package org.enso.librarymanager.resolved
|
||||||
|
|
||||||
|
import org.enso.librarymanager.published.repository.LibraryManifest
|
||||||
|
import org.enso.pkg.{Config, Package}
|
||||||
|
import org.enso.yaml.YamlHelper
|
||||||
|
|
||||||
|
import java.nio.file.Files
|
||||||
|
|
||||||
|
import scala.util.Try
|
||||||
|
|
||||||
|
/** Default filesystem read access to libraries.
|
||||||
|
*
|
||||||
|
* @param libraryPath the library location on the filesystem
|
||||||
|
*/
|
||||||
|
class FilesystemLibraryReadAccess(libraryPath: LibraryRoot)
|
||||||
|
extends LibraryReadAccess {
|
||||||
|
|
||||||
|
/** @inheritdoc */
|
||||||
|
override def readManifest(): Option[Try[LibraryManifest]] = {
|
||||||
|
val manifestPath = libraryPath.location.resolve(LibraryManifest.filename)
|
||||||
|
Option.when(Files.exists(manifestPath)) {
|
||||||
|
YamlHelper.load[LibraryManifest](manifestPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @inheritdoc */
|
||||||
|
override def readPackage(): Try[Config] = {
|
||||||
|
val configPath = libraryPath.location.resolve(Package.configFileName)
|
||||||
|
YamlHelper.load[Config](configPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,22 @@
|
|||||||
|
package org.enso.librarymanager.resolved
|
||||||
|
|
||||||
|
import org.enso.librarymanager.published.repository.LibraryManifest
|
||||||
|
import org.enso.pkg.Config
|
||||||
|
|
||||||
|
import scala.util.Try
|
||||||
|
|
||||||
|
/** Base trait allowing to read the library files on a filesystem. */
|
||||||
|
trait LibraryReadAccess {
|
||||||
|
|
||||||
|
/** Read the library manifest file.
|
||||||
|
*
|
||||||
|
* @return the library manifest, if the manifest file exists and `None` otherwise.
|
||||||
|
*/
|
||||||
|
def readManifest(): Option[Try[LibraryManifest]]
|
||||||
|
|
||||||
|
/** Read the library package config.
|
||||||
|
*
|
||||||
|
* @return the parsed library config.
|
||||||
|
*/
|
||||||
|
def readPackage(): Try[Config]
|
||||||
|
}
|
@ -0,0 +1,28 @@
|
|||||||
|
package org.enso.librarymanager.resolved
|
||||||
|
|
||||||
|
import java.nio.file.Path
|
||||||
|
|
||||||
|
/** The path to the library on a filesystem.
|
||||||
|
*
|
||||||
|
* @param location the library location on a filesystem
|
||||||
|
*/
|
||||||
|
case class LibraryRoot(location: Path)
|
||||||
|
object LibraryRoot {
|
||||||
|
|
||||||
|
/** Extension methods of [[LibraryRoot]]. */
|
||||||
|
implicit class LibraryRootMethods(val libraryPath: LibraryRoot)
|
||||||
|
extends AnyVal {
|
||||||
|
|
||||||
|
/** Get the read access to the library files. */
|
||||||
|
def getReadAccess: LibraryReadAccess =
|
||||||
|
new FilesystemLibraryReadAccess(libraryPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Syntax allowing to write nested paths in a more readable and concise way.
|
||||||
|
*/
|
||||||
|
implicit class LibraryRootSyntax(val libraryRoot: LibraryRoot)
|
||||||
|
extends AnyVal {
|
||||||
|
def /(other: String): Path = libraryRoot.location.resolve(other)
|
||||||
|
def /(other: Path): Path = libraryRoot.location.resolve(other)
|
||||||
|
}
|
||||||
|
}
|
@ -4,6 +4,7 @@ import nl.gn0s1s.bump.SemVer
|
|||||||
import org.enso.editions.Editions.Repository
|
import org.enso.editions.Editions.Repository
|
||||||
import org.enso.editions.{Editions, LibraryName, LibraryVersion}
|
import org.enso.editions.{Editions, LibraryName, LibraryVersion}
|
||||||
import org.enso.librarymanager.local.LocalLibraryProvider
|
import org.enso.librarymanager.local.LocalLibraryProvider
|
||||||
|
import org.enso.librarymanager.resolved.LibraryRoot
|
||||||
import org.enso.testkit.EitherValue
|
import org.enso.testkit.EitherValue
|
||||||
import org.scalatest.Inside
|
import org.scalatest.Inside
|
||||||
import org.scalatest.matchers.should.Matchers
|
import org.scalatest.matchers.should.Matchers
|
||||||
@ -57,8 +58,14 @@ class LibraryResolverSpec
|
|||||||
|
|
||||||
case class FakeLocalLibraryProvider(fixtures: Map[LibraryName, Path])
|
case class FakeLocalLibraryProvider(fixtures: Map[LibraryName, Path])
|
||||||
extends LocalLibraryProvider {
|
extends LocalLibraryProvider {
|
||||||
override def findLibrary(libraryName: LibraryName): Option[Path] =
|
|
||||||
fixtures.get(libraryName)
|
/** @inheritdoc */
|
||||||
|
override def findLibrary(
|
||||||
|
libraryName: LibraryName
|
||||||
|
): Option[LibraryRoot] =
|
||||||
|
fixtures
|
||||||
|
.get(libraryName)
|
||||||
|
.map(LibraryRoot(_))
|
||||||
}
|
}
|
||||||
|
|
||||||
val localLibraries = Map(
|
val localLibraries = Map(
|
||||||
|
@ -102,14 +102,10 @@ object ComponentGroup {
|
|||||||
/** The definition of a component group that extends an existing one.
|
/** The definition of a component group that extends an existing one.
|
||||||
*
|
*
|
||||||
* @param module the reference to the extended component group
|
* @param module the reference to the extended component group
|
||||||
* @param color the component group color
|
|
||||||
* @param icon the component group icon
|
|
||||||
* @param exports the list of components provided by this component group
|
* @param exports the list of components provided by this component group
|
||||||
*/
|
*/
|
||||||
case class ExtendedComponentGroup(
|
case class ExtendedComponentGroup(
|
||||||
module: ModuleReference,
|
module: ModuleReference,
|
||||||
color: Option[String],
|
|
||||||
icon: Option[String],
|
|
||||||
exports: Seq[Component]
|
exports: Seq[Component]
|
||||||
)
|
)
|
||||||
object ExtendedComponentGroup {
|
object ExtendedComponentGroup {
|
||||||
@ -117,22 +113,17 @@ object ExtendedComponentGroup {
|
|||||||
/** Fields for use when serializing the [[ExtendedComponentGroup]]. */
|
/** Fields for use when serializing the [[ExtendedComponentGroup]]. */
|
||||||
private object Fields {
|
private object Fields {
|
||||||
val Module = "module"
|
val Module = "module"
|
||||||
val Color = "color"
|
|
||||||
val Icon = "icon"
|
|
||||||
val Exports = "exports"
|
val Exports = "exports"
|
||||||
}
|
}
|
||||||
|
|
||||||
/** [[Encoder]] instance for the [[ExtendedComponentGroup]]. */
|
/** [[Encoder]] instance for the [[ExtendedComponentGroup]]. */
|
||||||
implicit val encoder: Encoder[ExtendedComponentGroup] = {
|
implicit val encoder: Encoder[ExtendedComponentGroup] = {
|
||||||
extendedComponentGroup =>
|
extendedComponentGroup =>
|
||||||
val color = extendedComponentGroup.color.map(Fields.Color -> _.asJson)
|
|
||||||
val icon = extendedComponentGroup.icon.map(Fields.Icon -> _.asJson)
|
|
||||||
val exports = Option.unless(extendedComponentGroup.exports.isEmpty)(
|
val exports = Option.unless(extendedComponentGroup.exports.isEmpty)(
|
||||||
Fields.Exports -> extendedComponentGroup.exports.asJson
|
Fields.Exports -> extendedComponentGroup.exports.asJson
|
||||||
)
|
)
|
||||||
Json.obj(
|
Json.obj(
|
||||||
(Fields.Module -> extendedComponentGroup.module.asJson) +:
|
(Fields.Module -> extendedComponentGroup.module.asJson) +: exports.toSeq: _*
|
||||||
(color.toSeq ++ icon.toSeq ++ exports.toSeq): _*
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -145,10 +136,8 @@ object ExtendedComponentGroup {
|
|||||||
Fields.Module,
|
Fields.Module,
|
||||||
json
|
json
|
||||||
)
|
)
|
||||||
color <- json.get[Option[String]](Fields.Color)
|
|
||||||
icon <- json.get[Option[String]](Fields.Icon)
|
|
||||||
exports <- json.getOrElse[List[Component]](Fields.Exports)(List())
|
exports <- json.getOrElse[List[Component]](Fields.Exports)(List())
|
||||||
} yield ExtendedComponentGroup(reference, color, icon, exports)
|
} yield ExtendedComponentGroup(reference, exports)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -219,7 +208,18 @@ object Shortcut {
|
|||||||
case class ModuleReference(
|
case class ModuleReference(
|
||||||
libraryName: LibraryName,
|
libraryName: LibraryName,
|
||||||
moduleName: ModuleName
|
moduleName: ModuleName
|
||||||
)
|
) {
|
||||||
|
|
||||||
|
/** The qualified name of the library consists of its prefix and name
|
||||||
|
* separated with a dot.
|
||||||
|
*/
|
||||||
|
def qualifiedName: String =
|
||||||
|
s"$libraryName${LibraryName.separator}${moduleName.name}"
|
||||||
|
|
||||||
|
/** @inheritdoc */
|
||||||
|
override def toString: String = qualifiedName
|
||||||
|
|
||||||
|
}
|
||||||
object ModuleReference {
|
object ModuleReference {
|
||||||
|
|
||||||
private def toModuleString(moduleReference: ModuleReference): String = {
|
private def toModuleString(moduleReference: ModuleReference): String = {
|
||||||
|
@ -134,8 +134,6 @@ class ConfigSpec
|
|||||||
LibraryName("Standard", "Base"),
|
LibraryName("Standard", "Base"),
|
||||||
ModuleName("Group 2")
|
ModuleName("Group 2")
|
||||||
),
|
),
|
||||||
color = None,
|
|
||||||
icon = None,
|
|
||||||
exports = List(Component("bax", None))
|
exports = List(Component("bax", None))
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
Loading…
Reference in New Issue
Block a user