Define Editions (#1797)

This commit is contained in:
Radosław Waśko 2021-06-18 16:39:45 +02:00 committed by GitHub
parent c819f42130
commit 241a1e7d74
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
138 changed files with 2669 additions and 578 deletions

View File

@ -65,6 +65,13 @@ jobs:
uses: actions/checkout@v2 uses: actions/checkout@v2
with: with:
path: repo path: repo
- name: Configure Pagefile (Windows)
if: runner.os == 'Windows'
uses: al-cheb/configure-pagefile-action@v1.2
with:
minimum-size: 16GB
maximum-size: 16GB
disk-root: "C:"
- name: Enable Developer Command Prompt (Windows) - name: Enable Developer Command Prompt (Windows)
uses: ilammy/msvc-dev-cmd@v1.9.0 uses: ilammy/msvc-dev-cmd@v1.9.0
- name: Disable TCP/UDP Offloading (macOS) - name: Disable TCP/UDP Offloading (macOS)

View File

@ -32,6 +32,13 @@ jobs:
uses: actions/checkout@v2 uses: actions/checkout@v2
with: with:
path: repo path: repo
- name: Configure Pagefile (Windows)
if: runner.os == 'Windows'
uses: al-cheb/configure-pagefile-action@v1.2
with:
minimum-size: 16GB
maximum-size: 16GB
disk-root: "C:"
- name: Enable Developer Command Prompt (Windows) - name: Enable Developer Command Prompt (Windows)
uses: ilammy/msvc-dev-cmd@v1.9.0 uses: ilammy/msvc-dev-cmd@v1.9.0
- name: Disable TCP/UDP Offloading (macOS) - name: Disable TCP/UDP Offloading (macOS)

View File

@ -27,6 +27,13 @@ jobs:
fail-fast: false fail-fast: false
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Configure Pagefile (Windows)
if: runner.os == 'Windows'
uses: al-cheb/configure-pagefile-action@v1.2
with:
minimum-size: 16GB
maximum-size: 16GB
disk-root: "C:"
- name: Enable Developer Command Prompt (Windows) - name: Enable Developer Command Prompt (Windows)
uses: ilammy/msvc-dev-cmd@v1.9.0 uses: ilammy/msvc-dev-cmd@v1.9.0
- name: Setup Go - name: Setup Go

View File

@ -39,6 +39,18 @@
- Added support for multiple content roots in the language server - Added support for multiple content roots in the language server
([#1800](https://github.com/enso-org/enso/pull/1800/)). It is not yet exposed ([#1800](https://github.com/enso-org/enso/pull/1800/)). It is not yet exposed
to the IDE, as this will be done as part of future work. to the IDE, as this will be done as part of future work.
- Modified the `package.yaml` format in preparation for the library ecosystem
([#1797](https://github.com/enso-org/enso/pull/1797)). The `engine-version`
field has been deprecated in favour of an `edition` field that allows to set
up the engine version and dependency resolution using the upcoming Edition
system. New tools will still be able to read the old format, but upon
modification, they will save changes in the new format. As the `edition` file
did not exist in the older version, old tools will actually correctly load the
migrated package file (as we allow for unknown fields), but they will not know
how to interpret the new `edition` field and so will fall back to using the
`default` engine version, which may be unexpected. Ideally, after migration,
the project should be used only with the new tools. The affected tools are the
Launcher and the Project Manager.
## Libraries ## Libraries

View File

@ -583,11 +583,11 @@ lazy val pkg = (project in file("lib/scala/pkg"))
version := "0.1", version := "0.1",
libraryDependencies ++= circe ++ Seq( libraryDependencies ++= circe ++ Seq(
"org.scalatest" %% "scalatest" % scalatestVersion % Test, "org.scalatest" %% "scalatest" % scalatestVersion % Test,
"nl.gn0s1s" %% "bump" % bumpVersion,
"io.circe" %% "circe-yaml" % circeYamlVersion, // separate from other circe deps because its independent project with its own versioning "io.circe" %% "circe-yaml" % circeYamlVersion, // separate from other circe deps because its independent project with its own versioning
"commons-io" % "commons-io" % commonsIoVersion "commons-io" % "commons-io" % commonsIoVersion
) )
) )
.dependsOn(editions)
lazy val `akka-native` = project lazy val `akka-native` = project
.in(file("lib/scala/akka-native")) .in(file("lib/scala/akka-native"))
@ -728,9 +728,12 @@ lazy val `project-manager` = (project in file("lib/scala/project-manager"))
) )
.dependsOn(`akka-native`) .dependsOn(`akka-native`)
.dependsOn(`version-output`) .dependsOn(`version-output`)
.dependsOn(pkg) .dependsOn(editions)
.dependsOn(cli)
.dependsOn(`polyglot-api`) .dependsOn(`polyglot-api`)
.dependsOn(`runtime-version-manager`) .dependsOn(`runtime-version-manager`)
.dependsOn(`library-manager`)
.dependsOn(pkg)
.dependsOn(`json-rpc-server`) .dependsOn(`json-rpc-server`)
.dependsOn(`json-rpc-server-test` % Test) .dependsOn(`json-rpc-server-test` % Test)
.dependsOn(testkit % Test) .dependsOn(testkit % Test)
@ -883,6 +886,7 @@ lazy val `polyglot-api` = project
.dependsOn(pkg) .dependsOn(pkg)
.dependsOn(`text-buffer`) .dependsOn(`text-buffer`)
.dependsOn(`logging-utils`) .dependsOn(`logging-utils`)
.dependsOn(testkit % Test)
lazy val `language-server` = (project in file("engine/language-server")) lazy val `language-server` = (project in file("engine/language-server"))
.settings( .settings(
@ -1184,8 +1188,53 @@ lazy val launcher = project
.dependsOn(`version-output`) .dependsOn(`version-output`)
.dependsOn(pkg) .dependsOn(pkg)
.dependsOn(`logging-service`) .dependsOn(`logging-service`)
.dependsOn(`distribution-manager` % Test)
.dependsOn(`runtime-version-manager-test` % Test) .dependsOn(`runtime-version-manager-test` % Test)
lazy val `distribution-manager` = project
.in(file("lib/scala/distribution-manager"))
.configs(Test)
.settings(
resolvers += Resolver.bintrayRepo("gn0s1s", "releases"),
libraryDependencies ++= Seq(
"com.typesafe.scala-logging" %% "scala-logging" % scalaLoggingVersion,
"io.circe" %% "circe-yaml" % circeYamlVersion,
"commons-io" % "commons-io" % commonsIoVersion,
"org.scalatest" %% "scalatest" % scalatestVersion % Test
)
)
.dependsOn(editions)
.dependsOn(pkg)
.dependsOn(`logging-utils`)
lazy val editions = project
.in(file("lib/scala/editions"))
.configs(Test)
.settings(
resolvers += Resolver.bintrayRepo("gn0s1s", "releases"),
libraryDependencies ++= Seq(
"com.typesafe.scala-logging" %% "scala-logging" % scalaLoggingVersion,
"nl.gn0s1s" %% "bump" % bumpVersion,
"io.circe" %% "circe-yaml" % circeYamlVersion,
"org.scalatest" %% "scalatest" % scalatestVersion % Test
)
)
lazy val `library-manager` = project
.in(file("lib/scala/library-manager"))
.configs(Test)
.settings(
resolvers += Resolver.bintrayRepo("gn0s1s", "releases"),
libraryDependencies ++= Seq(
"com.typesafe.scala-logging" %% "scala-logging" % scalaLoggingVersion,
"org.scalatest" %% "scalatest" % scalatestVersion % Test
)
)
.dependsOn(editions)
.dependsOn(cli)
.dependsOn(`distribution-manager`)
.dependsOn(testkit % Test)
lazy val `runtime-version-manager` = project lazy val `runtime-version-manager` = project
.in(file("lib/scala/runtime-version-manager")) .in(file("lib/scala/runtime-version-manager"))
.configs(Test) .configs(Test)
@ -1204,6 +1253,7 @@ lazy val `runtime-version-manager` = project
.dependsOn(`logging-service`) .dependsOn(`logging-service`)
.dependsOn(cli) .dependsOn(cli)
.dependsOn(`version-output`) .dependsOn(`version-output`)
.dependsOn(`distribution-manager`)
lazy val `runtime-version-manager-test` = project lazy val `runtime-version-manager-test` = project
.in(file("lib/scala/runtime-version-manager-test")) .in(file("lib/scala/runtime-version-manager-test"))
@ -1224,6 +1274,8 @@ lazy val `runtime-version-manager-test` = project
.dependsOn(`runtime-version-manager`) .dependsOn(`runtime-version-manager`)
.dependsOn(`logging-service`) .dependsOn(`logging-service`)
.dependsOn(testkit) .dependsOn(testkit)
.dependsOn(cli)
.dependsOn(`distribution-manager`)
lazy val `locking-test-helper` = project lazy val `locking-test-helper` = project
.in(file("lib/scala/locking-test-helper")) .in(file("lib/scala/locking-test-helper"))

View File

@ -26,6 +26,11 @@ The license information can be found along with the copyright notices.
Copyright notices related to this dependency can be found in the directory `com.thoughtworks.paranamer.paranamer-2.8`. Copyright notices related to this dependency can be found in the directory `com.thoughtworks.paranamer.paranamer-2.8`.
'slf4j-api', licensed under the MIT License, is distributed with the engine.
The license file can be found at `licenses/MIT`.
Copyright notices related to this dependency can be found in the directory `org.slf4j.slf4j-api-1.7.26`.
'izumi-reflect_2.13', licensed under the BSD-style, is distributed with the engine. 'izumi-reflect_2.13', licensed under the BSD-style, is distributed with the engine.
The license information can be found along with the copyright notices. The license information can be found along with the copyright notices.
Copyright notices related to this dependency can be found in the directory `dev.zio.izumi-reflect_2.13-1.0.0-M5`. Copyright notices related to this dependency can be found in the directory `dev.zio.izumi-reflect_2.13-1.0.0-M5`.
@ -246,11 +251,6 @@ The license file can be found at `licenses/APACHE2.0`.
Copyright notices related to this dependency can be found in the directory `com.typesafe.scala-logging.scala-logging_2.13-3.9.2`. Copyright notices related to this dependency can be found in the directory `com.typesafe.scala-logging.scala-logging_2.13-3.9.2`.
'slf4j-api', licensed under the MIT License, is distributed with the engine.
The license file can be found at `licenses/MIT`.
Copyright notices related to this dependency can be found in the directory `org.slf4j.slf4j-api-1.7.25`.
'commons-cli', licensed under the Apache License, Version 2.0, is distributed with the engine. 'commons-cli', licensed under the Apache License, Version 2.0, is distributed with the engine.
The license file can be found at `licenses/APACHE2.0`. The license file can be found at `licenses/APACHE2.0`.
Copyright notices related to this dependency can be found in the directory `commons-cli.commons-cli-1.4`. Copyright notices related to this dependency can be found in the directory `commons-cli.commons-cli-1.4`.

View File

@ -1,19 +1,3 @@
/*
* Copyright 2008 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* /*
* Copyright 2013 Google LLC * Copyright 2013 Google LLC
* *
@ -43,3 +27,19 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
/*
* Copyright 2008 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2014 The Error Prone Authors. * Copyright 2016 The Error Prone Authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -15,7 +15,7 @@
*/ */
/* /*
* Copyright 2016 The Error Prone Authors. * Copyright 2015 The Error Prone Authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -47,7 +47,7 @@
*/ */
/* /*
* Copyright 2015 The Error Prone Authors. * Copyright 2014 The Error Prone Authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.

View File

@ -1,7 +1,7 @@
/*
* Copyright (C) 2009-2020 Lightbend Inc. <https://www.lightbend.com>
*/
/* /*
* Copyright (C) 2017-2020 Lightbend Inc. <https://www.lightbend.com> * Copyright (C) 2017-2020 Lightbend Inc. <https://www.lightbend.com>
*/ */
/*
* Copyright (C) 2009-2020 Lightbend Inc. <https://www.lightbend.com>
*/

View File

@ -15,8 +15,6 @@
* limitations under the License. * limitations under the License.
*/ */
Copyright 2017-2019 John A. De Goes and the ZIO Contributors
/* /*
* Copyright 2017-2020 John A. De Goes and the ZIO Contributors * Copyright 2017-2020 John A. De Goes and the ZIO Contributors
* Copyright 2017-2018 Łukasz Biały, Paul Chiusano, Michael Pilquist, * Copyright 2017-2018 Łukasz Biały, Paul Chiusano, Michael Pilquist,
@ -36,3 +34,5 @@ Copyright 2017-2019 John A. De Goes and the ZIO Contributors
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
Copyright 2017-2019 John A. De Goes and the ZIO Contributors

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2011,2012 Mathias Doenitz, Johannes Rudolph * Copyright (C) 2011 Mathias Doenitz
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -15,7 +15,7 @@
*/ */
/* /*
* Copyright (C) 2011 Mathias Doenitz * Copyright (C) 2011,2012 Mathias Doenitz, Johannes Rudolph
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.

View File

@ -1,7 +1,7 @@
/*
* Copyright (C) 2009-2020 Lightbend Inc. <https://www.lightbend.com>
*/
/* /*
* Copyright (C) 2017-2020 Lightbend Inc. <https://www.lightbend.com> * Copyright (C) 2017-2020 Lightbend Inc. <https://www.lightbend.com>
*/ */
/*
* Copyright (C) 2009-2020 Lightbend Inc. <https://www.lightbend.com>
*/

View File

@ -15,8 +15,6 @@
* limitations under the License. * limitations under the License.
*/ */
Copyright 2017-2019 John A. De Goes and the ZIO Contributors
/* /*
* Copyright 2017-2020 John A. De Goes and the ZIO Contributors * Copyright 2017-2020 John A. De Goes and the ZIO Contributors
* Copyright 2017-2018 Łukasz Biały, Paul Chiusano, Michael Pilquist, * Copyright 2017-2018 Łukasz Biały, Paul Chiusano, Michael Pilquist,
@ -36,3 +34,5 @@ Copyright 2017-2019 John A. De Goes and the ZIO Contributors
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
Copyright 2017-2019 John A. De Goes and the ZIO Contributors

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2011,2012 Mathias Doenitz, Johannes Rudolph * Copyright (C) 2011 Mathias Doenitz
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -15,7 +15,7 @@
*/ */
/* /*
* Copyright (C) 2011 Mathias Doenitz * Copyright (C) 2011,2012 Mathias Doenitz, Johannes Rudolph
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.

View File

@ -47,3 +47,4 @@ It is broken up into categories as follows:
- [**Parser:**](./parser) Design and specification of the Enso parser. - [**Parser:**](./parser) Design and specification of the Enso parser.
- [**Infrastructure:**](./infrastructure) Description of the infrastructure for - [**Infrastructure:**](./infrastructure) Description of the infrastructure for
building Enso. building Enso.
- [**Libraries:**](./libraries) Description of Enso's Library Ecosystem.

View File

@ -20,7 +20,7 @@ structured and how it should behave.
- [Installing from a Portable Distribution](#installing-from-a-portable-distribution) - [Installing from a Portable Distribution](#installing-from-a-portable-distribution)
- [Layout of an Enso Version Package](#layout-of-an-enso-version-package) - [Layout of an Enso Version Package](#layout-of-an-enso-version-package)
- [Standard Library](#standard-library) - [Standard Library](#standard-library)
- [Resolvers](#resolvers) - [Enso Home Layout](#enso-home-layout)
<!-- /MarkdownTOC --> <!-- /MarkdownTOC -->
@ -64,19 +64,19 @@ extraction-location
│ └── 1.2.0 # A full distribution of given Enso version, described below. │ └── 1.2.0 # A full distribution of given Enso version, described below.
│ └── <truncated> │ └── <truncated>
├── runtime # A directory storing distributions of the JVM used by the Enso distributions. ├── runtime # A directory storing distributions of the JVM used by the Enso distributions.
│ └── graalvm-ce-27.1.1 │ └── graalvm-ce-java11-27.1.1
├── lib ├── lib # Contains sources of downloaded libraries.
│ └── src # Contains sources of downloaded libraries. │ └── Standard # Each prefix (usually corresponding to the author) is placed in a separate directory.
│ └── Dataframe # Each library may be stored in multiple version. │ └── Dataframe # Each library may be stored in multiple versions.
│ └── 1.7.0 # Each version contains a standard Enso package. │ └── 1.7.0 # Each version contains a standard Enso package.
│ ├── package.yaml │ ├── package.yaml
│ └── src │ └── src
│ ├── List.enso │ ├── List.enso
│ ├── Number.enso │ ├── Number.enso
│ └── Text.enso │ └── Text.enso
├── resolvers # Contains resolver specifications, described below. ├── editions # Contains Edition specifications.
│ ├── lts-1.56.7.yaml │ ├── 2021.4.yaml
│ └── lts-2.0.8.yaml │ └── nightly-2021-06-31.yaml
├── README.md # Information on layout and usage of the Enso distribution. ├── README.md # Information on layout and usage of the Enso distribution.
├── .enso.portable # A file that allows the universal launcher to detect that if it is run from this directory, it should run in portable distribution mode. ├── .enso.portable # A file that allows the universal launcher to detect that if it is run from this directory, it should run in portable distribution mode.
└── THIRD-PARTY # Contains licences of distributed components, including the NOTICE file. └── THIRD-PARTY # Contains licences of distributed components, including the NOTICE file.
@ -94,19 +94,19 @@ ENSO_DATA_DIRECTORY
│ └── 1.2.0 # A full distribution of given Enso version, described below. │ └── 1.2.0 # A full distribution of given Enso version, described below.
│ └── <truncated> │ └── <truncated>
├── runtime # A directory storing (optional) distributions of the JVM used by the Enso distributions. ├── runtime # A directory storing (optional) distributions of the JVM used by the Enso distributions.
│ └── graalvm-ce-27.1.1 │ └── graalvm-ce-java11-27.1.1
├── lib ├── lib # Contains sources of downloaded libraries.
│ └── src # Contains sources of downloaded libraries. │ └── Standard # Each prefix (usually corresponding to the author) is placed in a separate directory.
│ └── Dataframe # Each library may be stored in multiple version. │ └── Dataframe # Each library may be stored in multiple versions.
│ └── 1.7.0 # Each version contains a standard Enso package. │ └── 1.7.0 # Each version contains a standard Enso package.
│ ├── package.yaml │ ├── package.yaml
│ └── src │ └── src
│ ├── List.enso │ ├── List.enso
│ ├── Number.enso │ ├── Number.enso
│ └── Text.enso │ └── Text.enso
└── resolvers # Contains resolver specifications, described below. └── editions # Contains Edition specifications.
├── lts-1.56.7.yaml ├── 2021.4.yaml
└── lts-2.0.8.yaml └── nightly-2021-06-31.yaml
ENSO_CONFIG_DIRECTORY ENSO_CONFIG_DIRECTORY
└── global-config.yaml # Global user configuration. └── global-config.yaml # Global user configuration.
@ -202,20 +202,19 @@ but the following are some guidelines:
3. Packages that the compiler relies on, e.g. compile error definitions, stack 3. Packages that the compiler relies on, e.g. compile error definitions, stack
traces etc. traces etc.
### Resolvers ## Enso Home Layout
**Note** This system is not implemented yet. The location called in some places `<ENSO_HOME>` is the place where user's
projects and similar files are stored. Currently it is specified to always be
`$HOME/enso`.
A resolver is a manifest containing library versions that are automatically It has the following structure:
available for import in any project using the resolver.
Example contents of a resolver file are as follows: ```
<ENSO_HOME>
```yaml ├── projects # Contains all user projects.
enso-version: 1.0.7 ├── libraries # Contains all local libraries that can be edited by the user.
libraries: │ └── Prefix1 # Contains libraries with the given prefix.
- name: Base │ └── Library_Name # Contains a package of a local library.
version: 1.0.0 └── editions # Contains custom, user-defined editions that can be used as a base for project configurations.
- name: Http
version: 5.3.5
``` ```

View File

@ -102,19 +102,20 @@ The following is an example of this manifest file.
license: MIT license: MIT
name: My_Package name: My_Package
version: 1.0.1 version: 1.0.1
enso-version: 0.2.0 edition:
extends: 2021.3
enso-version: 0.2.12
libraries:
- name: Foo.Bar
version: 1.2.3
repository: main
prefer-local-libraries: false
authors: authors:
- name: John Doe - name: John Doe
email: john.doe@example.com email: john.doe@example.com
maintainers: maintainers:
- name: Jane Doe - name: Jane Doe
email: jane.doe@example.com email: jane.doe@example.com
resolver: lts-1.2.0
extra-dependencies:
- name: Base
version: "1.2.0"
- name: Http
version: "4.5.3"
``` ```
The following is the specification of the manifest fields. Fields marked as The following is the specification of the manifest fields. Fields marked as
@ -129,12 +130,37 @@ fields cannot be published.
package. Defaults to `None`, meaning the package is not safe for use by third package. Defaults to `None`, meaning the package is not safe for use by third
parties. parties.
#### edition
**Optional (required for publishing)** _Edition_: Defines the Edition settings
of the package that determine the engine version and library resolution
settings. It is a sub-setting that can consist of multiple fields, see
[the Edition documentation](../libraries/editions.md#the-edition-file) for the
format description.
The field was added in version 0.2.12 as a replacement for `enso-version` as it
supersedes its functionality.
If the `edition` field is not specified, a default edition is used.
#### enso-version #### enso-version
**Optional (required for publishing)** _String_: Specifies the Enso version that **Deprecated** _String_: Specifies the Enso version that should be used for this
should be used for this project. If not set or set to `default`, the default project. If not set or set to `default`, the default locally installed Enso
locally installed Enso version will be used. The version should not be `default` version will be used.
if the package is to be published.
The field was deprecated in version 0.2.12. Currently it is still supported, but
the newer tools will migrate it to the `edition` format when the config is
modified.
If old tools see a config file that includes an `edition` setting but does not
include the `engine-version` (for example after the migration), they will fall
back to using the default engine version - that is because old tools were not
aware of the `edition` field, so they will simply ignore it.
If a config defines the `edition` field it should not define the
`engine-version` field anymore, as that could lead to inconsistent engine
version settings.
#### version #### version
@ -163,32 +189,18 @@ present.
**Optional** _List of contacts_: The name(s) and contact info(s) of the current **Optional** _List of contacts_: The name(s) and contact info(s) of the current
maintainer(s) of this library, in the same format as `authors` above. maintainer(s) of this library, in the same format as `authors` above.
#### resolver #### prefer-local-libraries
**Note** This field is not currently implemented. **Optional (required for **Optional** _Boolean_: A flag that tells the library resolver to prefer local
publishing)** _String_: The resolver name, used to choose compiler version and library versions over the ones specified by the edition configuration. This is
basic libraries set. If not set, the system-default resolver will be used. useful to make all local libraries easily accessible, but in more sophisticated
scenarios individual `local` repository overrides should be used instead of
that. See [Library Resolution](../libraries/editions.md#library-resolution) for
more details.
> The actionables for this section are: If the flag is not specified, it defaults to `false`, delegating all library
> resolution to the edition configuration. However, newly created projects will
> - Extend the compiler version to handle version bounds. have it set to `true`.
#### extra-dependencies
**Note** This field is not currently implemented. **Optional** _List of Library
objects_: The list of libraries this package requires to function properly and
that are not included in the resolver. Defaults to an empty list.
A library object is of the form:
```yaml
name: <name of the library>
version: <semver string of the required library version>
```
> The actionables for this section are:
>
> - Extend the library version field to handle version bounds.
### The `visualization` Directory ### The `visualization` Directory

14
docs/libraries/README.md Normal file
View File

@ -0,0 +1,14 @@
---
layout: section-summary
title: Enso Library Ecosystem
category: libraries
tags: [libraries, readme]
order: 0
---
# Enso Library Ecosystem
Documents in this section describe Enso's library ecosystem.
- [**Editions:**](./editions.md) Information on Editions, the concept that
organizes library versioning.

217
docs/libraries/editions.md Normal file
View File

@ -0,0 +1,217 @@
---
layout: developer-doc
title: Editions
category: libraries
tags: [libraries, editions]
order: 1
---
# Editions
This document describes the concept of Editions.
<!-- MarkdownTOC levels="2,3" autolink="true" -->
- [What Is An Edition](#what-is-an-edition)
- [The Edition File](#the-edition-file)
- [Repositories](#repositories)
- [Libraries](#libraries)
- [Extending the Editions](#extending-the-editions)
- [An Example Configuration](#an-example-configuration)
- [Edition Resolution](#edition-resolution)
- [Updating the Editions](#updating-the-editions)
- [Library Resolution](#library-resolution)
<!-- /MarkdownTOC -->
## What Is An Edition
An Edition, is in principle, a list of library versions that should be
compatible with each other. An Edition specifies the engine version and a set of
library versions that can be used together.
If a library included in an Edition depends on another library, that other
library must also be included in that Edition and the version at which it is
included must be compatible with the library that depends on it. Each Edition
may only include a single version of each library.
Thus, when a library is to be installed, its version is uniquely determined by
the selected Edition. A curated Edition file will guarantee that all libraries
are compatible which simplifies version resolution - the exact version that is
specified in the Edition is always used.
## The Edition File
The Edition file is a YAML file that can contain the following fields:
- `engine-version` which should be a semantic versioning string specifying the
engine version that should be associated with that edition,
- `repositories` which defines the repositories which are sources of library
packages, its format is [described below](#repositories),
- `extends` which can contain a name of another Edition that this Edition
extends,
- `libraries` which defines the libraries that this Edition should include, its
format is [described below](#libraries).
Every field is optional, but for an Edition file to be valid it must specify at
least the engine version to be used (either by specifying it directly or
extending another edition that specifies it).
### Repositories
The `repositories` field is a list of repository objects.
Each object must have:
- a `name` field which specifies the name under which this repository will be
referred to in the rest of the file,
- a `url` field which specifies the URL of the root of that repository.
The `name` can be any string which only needs to be consistent with the names
used in the package definitions. The only reserved name is `local`, which is a
special repository name, as [explained below](#library-resolution).
### Libraries
The `libraries` field defines the set of libraries included in the edition.
Each library is represented by an object that must have:
- a `name` field which is the fully qualified name of the library (consisting of
its prefix and the name itself),
- a `repository` field which specifies which repository this package should be
downloaded from. The `repository` field should refer to the `name` of one of
the repositories defined in the edition or to `local`,
- a `version` field which specifies which exact package version should be used
when the library is imported; it is normally required, but if the `repository`
is set to `local`, the version must not be specified as the version will only
depend on what is available in the local repository,
- an optional `hash` that can be included to verify the integrity of the
package.
The `hash` field is currently not implemented.
### Extending the Editions
An edition may extend another one by using the `extends` property specifying the
name of the edition that is to be extended. Henceforth we will call the edition
that is being extended 'the parent edition' and the other one 'the local
edition'.
The current edition inherits all configuration of the parent edition, but it can
also override specific settings.
If the `engine-version` is specified in the current edition, it overrides the
engine version that was implied from the parent edition.
If the current edition specifies its libraries, they are added to the set of
available libraries defined by the parent edition. If the current edition
defines a library that has the same fully qualified name as a library that was
already defined in the parent edition, the definition from the current edition
takes precedence. This is the most important mechanism of extending editions
that allows to override library settings.
The libraries defined in the current edition can refer to the repositories
defined both in the current edition and in the parent. However, if the current
edition defines a repository with the same name as some repository defined in
the parent edition, the definition from the current edition takes precedence for
the package definitions of the current definition, **but** the package
definitions in the parent edition are not affected (they still refer to the
definition from the their own edition). So you can shadow a repository name, but
you cannot override it for libraries from the parent edition - instead libraries
whose repository should be changed must all be overridden in the current
edition.
Extending editions can be arbitrarily nested. That is, an edition can extend
another edition that extends another one etc. The only limitation is that
obviously there can be no cycles in the chain of extensions. Multiple extensions
are resolved as follows: first the parent edition is completely resolved (which
may recursively need to first resolve its parents etc.) and only then the
current edition applies its overrides.
### An Example Configuration
```yaml
extends: 2021.4
engine-version: 1.2.3
repositories:
- name: secondary
url: https://example.com/
libraries:
- name: Foo.Bar
version: 1.0.0
repository: secondary
```
The edition file shown above extends a base edition file called `2021.4`. It
overrides the engine version set in the parent edition to `1.2.3`. Moreover it
adds a library `Foo.Bar` from the `secondary` repository, or if `2021.4`
included the library `Foo.Bar`, its definition is overridden with the one
provided here.
## Edition Resolution
The edition configuration for a project is loaded from the `edition` section in
its `package.yaml` configuration. This 'per-project' edition has no assigned
names, but it can refer to other editions by their names (when extending them).
These editions are resolved using the logic below:
1. Each `<edition-name>` corresponds to a file `<edition-name>.yaml`.
2. First, the directory `<ENSO_HOME>/editions` is scanned for a matching edition
file.
3. If none is found above, the directory `$ENSO_DATA_DIRECTORY/editions` is
checked.
See [The Enso Distribution](../distribution/distribution.md) for definitions of
the directories.
### Updating the Editions
The global user configuration file should contain a list of URLs specifying
edition providers that should be used. By default (if the field is missing), it
will default to our official edition provider, but users may add other providers
or remove the official one.
When `enso update-editions` is called or when requested by the IDE, these
providers are queried and any new edition files are downloaded to the
`$ENSO_DATA_DIRECTORY/editions` directory. Editions are assumed to be immutable,
so edition files that already exist on disk are not redownloaded.
## Library Resolution
Below are listed the steps that are taken when resolving an import of library
`Foo.Bar`:
1. If and only if the project has `prefer-local-libraries` set to `true` and if
any directory on the library path contains `Foo/Bar`, that local instance is
chosen as the library that should be used, regardless of the version that is
there;
2. Otherwise, the list of libraries defined directly in the `edition` section of
`package.yaml` of the current project is checked, and if the library is
defined there, it is selected.
3. Otherwise, any parent editions are consulted; if they too do not contain the
library that we are searching for, an error is reported.
4. Once we know the library version to be used:
1. If the repository associated with the library is `local`, the library path
is searched for the first directory to contain `Foo/Bar` and this path is
loaded. If the library is not present on the library path, an error is
reported.
2. Otherwise, the edition must have defined an exact `<version>` of the
library that is supposed to be used.
3. If the library is already downloaded in the local repository cache, that
is the directory `$ENSO_DATA_DIRECTORY/lib/Foo/Bar/<version>` exists, that
package is loaded.
4. Otherwise, the library is missing and must be downloaded from its
associated repository (and placed in the cache as above).
By default, the library path is `<ENSO_HOME>/libraries/` but it can be
overridden by setting the `ENSO_LIBRARY_PATH` environment variable. It may
include a list of directories (separated by the system specific path separator);
the first directory on the list has the highest precedence.
In particular, if `prefer-local-libraries` is `false`, and the edition does not
define a library at all, when trying to resolve such a library, it is reported
as not found even if a local version of it exists. That is because
auto-discovery of local libraries is only done with `prefer-local-libraries` set
to `true`. In all other cases, the `local` repository overrides should be set
explicitly.

View File

@ -1,10 +1,10 @@
package org.enso.launcher package org.enso.launcher
import java.nio.file.Path import java.nio.file.Path
import com.typesafe.scalalogging.Logger import com.typesafe.scalalogging.Logger
import io.circe.Json import io.circe.Json
import nl.gn0s1s.bump.SemVer import nl.gn0s1s.bump.SemVer
import org.enso.distribution.EditionManager
import org.enso.runtimeversionmanager.CurrentVersion import org.enso.runtimeversionmanager.CurrentVersion
import org.enso.runtimeversionmanager.config.{ import org.enso.runtimeversionmanager.config.{
DefaultVersion, DefaultVersion,
@ -42,12 +42,14 @@ case class Launcher(cliOptions: GlobalCLIOptions) {
runtimeVersionManager(cliOptions, alwaysInstallMissing = false) runtimeVersionManager(cliOptions, alwaysInstallMissing = false)
private lazy val configurationManager = private lazy val configurationManager =
new GlobalConfigurationManager(componentsManager, distributionManager) new GlobalConfigurationManager(componentsManager, distributionManager)
private lazy val projectManager = new ProjectManager(configurationManager) private lazy val editionManager = EditionManager(distributionManager)
private lazy val projectManager = new ProjectManager
private lazy val runner = private lazy val runner =
new LauncherRunner( new LauncherRunner(
projectManager, projectManager,
configurationManager, configurationManager,
componentsManager, componentsManager,
editionManager,
LauncherEnvironment, LauncherEnvironment,
LauncherLogging.loggingServiceEndpoint() LauncherLogging.loggingServiceEndpoint()
) )

View File

@ -3,12 +3,12 @@ package org.enso.launcher.cli
import com.typesafe.scalalogging.Logger import com.typesafe.scalalogging.Logger
import nl.gn0s1s.bump.SemVer import nl.gn0s1s.bump.SemVer
import org.enso.cli.CLIOutput import org.enso.cli.CLIOutput
import org.enso.distribution.locking.Resource
import org.enso.launcher.InfoLogger import org.enso.launcher.InfoLogger
import org.enso.runtimeversionmanager.components.{ import org.enso.runtimeversionmanager.components.{
GraalVMVersion, GraalVMVersion,
RuntimeVersionManagementUserInterface RuntimeVersionManagementUserInterface
} }
import org.enso.runtimeversionmanager.locking.Resource
/** [[RuntimeVersionManagementUserInterface]] that reports information and progress /** [[RuntimeVersionManagementUserInterface]] that reports information and progress
* to the command line. * to the command line.

View File

@ -2,13 +2,13 @@ package org.enso.launcher.cli
import java.io.IOException import java.io.IOException
import java.nio.file.{Files, NoSuchFileException, Path} import java.nio.file.{Files, NoSuchFileException, Path}
import cats.implicits._ import cats.implicits._
import nl.gn0s1s.bump.SemVer import nl.gn0s1s.bump.SemVer
import org.enso.cli.arguments.Opts import org.enso.cli.arguments.Opts
import org.enso.cli.arguments.Opts.implicits._ import org.enso.cli.arguments.Opts.implicits._
import org.enso.runtimeversionmanager.{CurrentVersion, FileSystem, OS} import org.enso.distribution.{FileSystem, OS}
import org.enso.runtimeversionmanager.FileSystem.PathSyntax import org.enso.runtimeversionmanager.CurrentVersion
import org.enso.distribution.FileSystem.PathSyntax
import org.enso.runtimeversionmanager.cli.Arguments._ import org.enso.runtimeversionmanager.cli.Arguments._
import org.enso.launcher.distribution.LauncherEnvironment import org.enso.launcher.distribution.LauncherEnvironment
import org.enso.launcher.upgrade.LauncherUpgrader import org.enso.launcher.upgrade.LauncherUpgrader

View File

@ -1,10 +1,9 @@
package org.enso.launcher.cli package org.enso.launcher.cli
import java.nio.file.{Files, Path} import java.nio.file.{Files, Path}
import org.enso.cli.arguments import org.enso.cli.arguments
import org.enso.cli.arguments.CommandHelp import org.enso.cli.arguments.CommandHelp
import org.enso.runtimeversionmanager.{Environment, FileSystem} import org.enso.distribution.{Environment, FileSystem}
import org.enso.launcher.distribution.LauncherEnvironment import org.enso.launcher.distribution.LauncherEnvironment
import scala.sys.process._ import scala.sys.process._

View File

@ -1,22 +1,15 @@
package org.enso.launcher.components package org.enso.launcher.components
import java.nio.file.{Files, Path}
import akka.http.scaladsl.model.Uri import akka.http.scaladsl.model.Uri
import nl.gn0s1s.bump.SemVer import nl.gn0s1s.bump.SemVer
import org.enso.runtimeversionmanager.Environment import org.enso.distribution.{EditionManager, Environment}
import org.enso.runtimeversionmanager.components.RuntimeVersionManager
import org.enso.runtimeversionmanager.config.GlobalConfigurationManager
import org.enso.runtimeversionmanager.runner.{
LanguageServerOptions,
RunSettings,
Runner,
RunnerError,
WhichEngine
}
import org.enso.launcher.project.ProjectManager import org.enso.launcher.project.ProjectManager
import org.enso.loggingservice.LogLevel import org.enso.loggingservice.LogLevel
import org.enso.runtimeversionmanager.components.RuntimeVersionManager
import org.enso.runtimeversionmanager.config.GlobalConfigurationManager
import org.enso.runtimeversionmanager.runner._
import java.nio.file.{Files, Path}
import scala.concurrent.Future import scala.concurrent.Future
import scala.util.Try import scala.util.Try
@ -26,10 +19,13 @@ class LauncherRunner(
projectManager: ProjectManager, projectManager: ProjectManager,
configurationManager: GlobalConfigurationManager, configurationManager: GlobalConfigurationManager,
componentsManager: RuntimeVersionManager, componentsManager: RuntimeVersionManager,
editionManager: EditionManager,
environment: Environment, environment: Environment,
loggerConnection: Future[Option[Uri]] loggerConnection: Future[Option[Uri]]
) extends Runner( ) extends Runner(
componentsManager, componentsManager,
configurationManager,
editionManager,
environment, environment,
loggerConnection loggerConnection
) { ) {
@ -53,12 +49,7 @@ class LauncherRunner(
projectManager.findProject(currentWorkingDirectory).get projectManager.findProject(currentWorkingDirectory).get
} }
val version = val version = resolveVersion(versionOverride, inProject)
versionOverride.getOrElse {
inProject
.map(_.version)
.getOrElse(configurationManager.defaultVersion)
}
val arguments = inProject match { val arguments = inProject match {
case Some(project) => case Some(project) =>
val projectPackagePath = val projectPackagePath =
@ -110,9 +101,7 @@ class LauncherRunner(
val project = val project =
if (projectMode) Some(projectManager.loadProject(actualPath).get) if (projectMode) Some(projectManager.loadProject(actualPath).get)
else projectManager.findProject(actualPath).get else projectManager.findProject(actualPath).get
val version = versionOverride val version = resolveVersion(versionOverride, project)
.orElse(project.map(_.version))
.getOrElse(configurationManager.defaultVersion)
val arguments = val arguments =
if (projectMode) Seq("--run", actualPath.toString) if (projectMode) Seq("--run", actualPath.toString)
@ -184,8 +173,7 @@ class LauncherRunner(
for { for {
project <- projectManager.findProject(currentWorkingDirectory) project <- projectManager.findProject(currentWorkingDirectory)
} yield { } yield {
val version = val version = resolveVersion(None, project)
project.map(_.version).getOrElse(configurationManager.defaultVersion)
val arguments = val arguments =
Seq("--version") ++ (if (useJSON) Seq("--json") else Seq()) Seq("--version") ++ (if (useJSON) Seq("--json") else Seq())

View File

@ -1,5 +1,10 @@
package org.enso.launcher.distribution package org.enso.launcher.distribution
import org.enso.distribution.PortableDistributionManager
import org.enso.distribution.locking.{
ResourceManager,
ThreadSafeFileLockManager
}
import org.enso.launcher.cli.{ import org.enso.launcher.cli.{
CLIRuntimeVersionManagementUserInterface, CLIRuntimeVersionManagementUserInterface,
GlobalCLIOptions GlobalCLIOptions
@ -11,14 +16,7 @@ import org.enso.runtimeversionmanager.components.{
RuntimeComponentUpdaterFactory, RuntimeComponentUpdaterFactory,
RuntimeVersionManager RuntimeVersionManager
} }
import org.enso.runtimeversionmanager.distribution.{ import org.enso.runtimeversionmanager.distribution.TemporaryDirectoryManager
PortableDistributionManager,
TemporaryDirectoryManager
}
import org.enso.runtimeversionmanager.locking.{
ResourceManager,
ThreadSafeFileLockManager
}
import org.enso.runtimeversionmanager.releases.engine.EngineRepository import org.enso.runtimeversionmanager.releases.engine.EngineRepository
import org.enso.runtimeversionmanager.releases.graalvm.GraalCEReleaseProvider import org.enso.runtimeversionmanager.releases.graalvm.GraalCEReleaseProvider

View File

@ -1,9 +1,8 @@
package org.enso.launcher.distribution package org.enso.launcher.distribution
import java.nio.file.Path import java.nio.file.Path
import com.typesafe.scalalogging.Logger import com.typesafe.scalalogging.Logger
import org.enso.runtimeversionmanager.Environment import org.enso.distribution.Environment
/** Default [[Environment]] to use in the launcher. /** Default [[Environment]] to use in the launcher.
* *

View File

@ -1,14 +1,17 @@
package org.enso.launcher.installation package org.enso.launcher.installation
import java.nio.file.{Files, Path} import java.nio.file.{Files, Path}
import com.typesafe.scalalogging.Logger import com.typesafe.scalalogging.Logger
import org.enso.cli.CLIOutput import org.enso.cli.CLIOutput
import org.enso.runtimeversionmanager.FileSystem.PathSyntax import org.enso.distribution.{
DistributionManager,
FileSystem,
OS,
PortableDistributionManager
}
import org.enso.distribution.locking.ResourceManager
import org.enso.distribution.FileSystem.PathSyntax
import org.enso.runtimeversionmanager.config.GlobalConfigurationManager import org.enso.runtimeversionmanager.config.GlobalConfigurationManager
import org.enso.runtimeversionmanager.distribution.PortableDistributionManager
import org.enso.runtimeversionmanager.locking.ResourceManager
import org.enso.runtimeversionmanager.{FileSystem, OS}
import org.enso.launcher.InfoLogger import org.enso.launcher.InfoLogger
import org.enso.launcher.cli.{GlobalCLIOptions, InternalOpts, Main} import org.enso.launcher.cli.{GlobalCLIOptions, InternalOpts, Main}
import org.enso.launcher.distribution.DefaultManagers import org.enso.launcher.distribution.DefaultManagers
@ -54,9 +57,9 @@ class DistributionInstaller(
private val nonEssentialDirectories = Seq("THIRD-PARTY") private val nonEssentialDirectories = Seq("THIRD-PARTY")
private val enginesDirectory = private val enginesDirectory =
installed.dataDirectory / manager.ENGINES_DIRECTORY installed.dataDirectory / DistributionManager.ENGINES_DIRECTORY
private val runtimesDirectory = private val runtimesDirectory =
installed.dataDirectory / manager.RUNTIMES_DIRECTORY installed.dataDirectory / DistributionManager.RUNTIMES_DIRECTORY
/** Installs the distribution under configured location. /** Installs the distribution under configured location.
* *

View File

@ -1,15 +1,18 @@
package org.enso.launcher.installation package org.enso.launcher.installation
import java.nio.file.{Files, Path} import java.nio.file.{Files, Path}
import com.typesafe.scalalogging.Logger import com.typesafe.scalalogging.Logger
import org.apache.commons.io.FileUtils import org.apache.commons.io.FileUtils
import org.enso.cli.CLIOutput import org.enso.cli.CLIOutput
import org.enso.runtimeversionmanager.FileSystem.PathSyntax import org.enso.distribution.{
DistributionManager,
FileSystem,
OS,
PortableDistributionManager
}
import org.enso.distribution.locking.ResourceManager
import org.enso.distribution.FileSystem.PathSyntax
import org.enso.runtimeversionmanager.config.GlobalConfigurationManager import org.enso.runtimeversionmanager.config.GlobalConfigurationManager
import org.enso.runtimeversionmanager.distribution.PortableDistributionManager
import org.enso.runtimeversionmanager.locking.ResourceManager
import org.enso.runtimeversionmanager.{FileSystem, OS}
import org.enso.launcher.InfoLogger import org.enso.launcher.InfoLogger
import org.enso.launcher.cli.{ import org.enso.launcher.cli.{
GlobalCLIOptions, GlobalCLIOptions,
@ -178,7 +181,7 @@ class DistributionUninstaller(
private val knownDataDirectories = private val knownDataDirectories =
Set.from( Set.from(
manager.LocallyInstalledDirectories.possibleDirectoriesInsideDataDirectory manager.LocallyInstalledDirectories.possibleDirectoriesInsideDataDirectory
) - manager.LOCK_DIRECTORY ) - DistributionManager.LOCK_DIRECTORY
/** Removes all files contained in the ENSO_DATA_DIRECTORY and possibly the /** Removes all files contained in the ENSO_DATA_DIRECTORY and possibly the
* directory itself. * directory itself.
@ -211,7 +214,7 @@ class DistributionUninstaller(
resourceManager.unlockTemporaryDirectory() resourceManager.unlockTemporaryDirectory()
resourceManager.releaseMainLock() resourceManager.releaseMainLock()
val lockDirectory = dataRoot / manager.LOCK_DIRECTORY val lockDirectory = dataRoot / DistributionManager.LOCK_DIRECTORY
if (Files.isDirectory(lockDirectory)) { if (Files.isDirectory(lockDirectory)) {
for (lock <- FileSystem.listDirectory(lockDirectory)) { for (lock <- FileSystem.listDirectory(lockDirectory)) {
try { try {

View File

@ -1,11 +1,9 @@
package org.enso.launcher.project package org.enso.launcher.project
import java.nio.file.Path
import org.enso.runtimeversionmanager.config.GlobalConfigurationManager
import org.enso.runtimeversionmanager.runner.Project
import org.enso.pkg.PackageManager import org.enso.pkg.PackageManager
import org.enso.runtimeversionmanager.runner.Project
import java.nio.file.Path
import scala.util.{Failure, Try} import scala.util.{Failure, Try}
/** A helper class for project management. /** A helper class for project management.
@ -13,7 +11,7 @@ import scala.util.{Failure, Try}
* It allows to create new project, open existing ones or traverse the * It allows to create new project, open existing ones or traverse the
* directory tree to find a project based on a path inside it. * directory tree to find a project based on a path inside it.
*/ */
class ProjectManager(globalConfigurationManager: GlobalConfigurationManager) { class ProjectManager {
private val packageManager = PackageManager.Default private val packageManager = PackageManager.Default
@ -22,7 +20,7 @@ class ProjectManager(globalConfigurationManager: GlobalConfigurationManager) {
def loadProject(path: Path): Try[Project] = def loadProject(path: Path): Try[Project] =
packageManager packageManager
.loadPackage(path.toFile) .loadPackage(path.toFile)
.map(new Project(_, globalConfigurationManager)) .map(new Project(_))
.recoverWith(error => Failure(ProjectLoadingError(path, error))) .recoverWith(error => Failure(ProjectLoadingError(path, error)))
/** Traverses the directory tree looking for a project in one of the ancestors /** Traverses the directory tree looking for a project in one of the ancestors
@ -40,7 +38,7 @@ class ProjectManager(globalConfigurationManager: GlobalConfigurationManager) {
private def tryFindingProject(root: Path): Try[Project] = private def tryFindingProject(root: Path): Try[Project] =
packageManager packageManager
.loadPackage(root.toFile) .loadPackage(root.toFile)
.map(new Project(_, globalConfigurationManager)) .map(new Project(_))
.recoverWith { .recoverWith {
case PackageManager.PackageNotFound() if root.getParent != null => case PackageManager.PackageNotFound() if root.getParent != null =>
tryFindingProject(root.getParent) tryFindingProject(root.getParent)

View File

@ -3,7 +3,7 @@ package org.enso.launcher.releases.launcher
import io.circe.{yaml, Decoder} import io.circe.{yaml, Decoder}
import nl.gn0s1s.bump.SemVer import nl.gn0s1s.bump.SemVer
import org.enso.runtimeversionmanager.releases.ReleaseProviderException import org.enso.runtimeversionmanager.releases.ReleaseProviderException
import org.enso.pkg.SemVerJson._ import org.enso.editions.SemVerJson._
import scala.util.{Failure, Try} import scala.util.{Failure, Try}

View File

@ -1,32 +1,32 @@
package org.enso.launcher.upgrade package org.enso.launcher.upgrade
import java.nio.file.{Files, Path} import java.nio.file.{Files, Path}
import com.typesafe.scalalogging.Logger import com.typesafe.scalalogging.Logger
import nl.gn0s1s.bump.SemVer import nl.gn0s1s.bump.SemVer
import org.enso.cli.CLIOutput import org.enso.cli.CLIOutput
import org.enso.runtimeversionmanager.{CurrentVersion, FileSystem, OS} import org.enso.distribution.{DistributionManager, FileSystem, OS}
import org.enso.runtimeversionmanager.FileSystem.PathSyntax import org.enso.distribution.locking.{
import org.enso.runtimeversionmanager.archive.Archive
import org.enso.runtimeversionmanager.components.UpgradeRequiredError
import org.enso.runtimeversionmanager.distribution.DistributionManager
import org.enso.launcher.cli.{
CLIProgressReporter,
GlobalCLIOptions,
InternalOpts
}
import org.enso.runtimeversionmanager.locking.{
LockType, LockType,
LockUserInterface, LockUserInterface,
Resource, Resource,
ResourceManager ResourceManager
} }
import org.enso.runtimeversionmanager.CurrentVersion
import org.enso.distribution.FileSystem.PathSyntax
import org.enso.runtimeversionmanager.archive.Archive
import org.enso.runtimeversionmanager.components.UpgradeRequiredError
import org.enso.launcher.cli.{
CLIProgressReporter,
GlobalCLIOptions,
InternalOpts
}
import org.enso.launcher.releases.launcher.LauncherRelease import org.enso.launcher.releases.launcher.LauncherRelease
import org.enso.runtimeversionmanager.releases.ReleaseProvider import org.enso.runtimeversionmanager.releases.ReleaseProvider
import org.enso.launcher.releases.LauncherRepository import org.enso.launcher.releases.LauncherRepository
import org.enso.launcher.InfoLogger import org.enso.launcher.InfoLogger
import org.enso.launcher.distribution.DefaultManagers import org.enso.launcher.distribution.DefaultManagers
import org.enso.logger.LoggerSyntax import org.enso.logger.LoggerSyntax
import org.enso.runtimeversionmanager.locking.Resources
import scala.util.Try import scala.util.Try
import scala.util.control.NonFatal import scala.util.control.NonFatal
@ -66,7 +66,7 @@ class LauncherUpgrader(
} }
resourceManager.withResource( resourceManager.withResource(
failIfAnotherUpgradeIsRunning, failIfAnotherUpgradeIsRunning,
Resource.LauncherExecutable, Resources.LauncherExecutable,
LockType.Exclusive LockType.Exclusive
) { ) {
runCleanup(isStartup = true) runCleanup(isStartup = true)

View File

@ -1,8 +1,8 @@
package org.enso.launcher package org.enso.launcher
import java.nio.file.{Files, Path} import org.enso.distribution.OS
import org.enso.runtimeversionmanager.OS import java.nio.file.{Files, Path}
import org.enso.runtimeversionmanager.test.NativeTestHelper import org.enso.runtimeversionmanager.test.NativeTestHelper
import org.scalatest.concurrent.{Signaler, TimeLimitedTests} import org.scalatest.concurrent.{Signaler, TimeLimitedTests}
import org.scalatest.matchers.should.Matchers import org.scalatest.matchers.should.Matchers

View File

@ -4,7 +4,8 @@ import java.nio.file.{Files, Path}
import java.util.UUID import java.util.UUID
import akka.http.scaladsl.model.Uri import akka.http.scaladsl.model.Uri
import nl.gn0s1s.bump.SemVer import nl.gn0s1s.bump.SemVer
import org.enso.runtimeversionmanager.FileSystem.PathSyntax import org.enso.distribution.EditionManager
import org.enso.distribution.FileSystem.PathSyntax
import org.enso.runtimeversionmanager.config.GlobalConfigurationManager import org.enso.runtimeversionmanager.config.GlobalConfigurationManager
import org.enso.runtimeversionmanager.runner._ import org.enso.runtimeversionmanager.runner._
import org.enso.runtimeversionmanager.test.RuntimeVersionManagerTest import org.enso.runtimeversionmanager.test.RuntimeVersionManagerTest
@ -30,13 +31,15 @@ class LauncherRunnerSpec extends RuntimeVersionManagerTest {
new GlobalConfigurationManager(componentsManager, distributionManager) { new GlobalConfigurationManager(componentsManager, distributionManager) {
override def defaultVersion: SemVer = defaultEngineVersion override def defaultVersion: SemVer = defaultEngineVersion
} }
val projectManager = new ProjectManager(configurationManager) val editionManager = EditionManager(distributionManager)
val projectManager = new ProjectManager()
val cwd = cwdOverride.getOrElse(getTestDirectory) val cwd = cwdOverride.getOrElse(getTestDirectory)
val runner = val runner =
new LauncherRunner( new LauncherRunner(
projectManager, projectManager,
configurationManager, configurationManager,
componentsManager, componentsManager,
editionManager,
env, env,
Future.successful(Some(fakeUri)) Future.successful(Some(fakeUri))
) { ) {

View File

@ -1,9 +1,9 @@
package org.enso.launcher.installation package org.enso.launcher.installation
import java.nio.file.{Files, Path} import org.enso.distribution.{FileSystem, OS}
import org.enso.runtimeversionmanager.{FileSystem, OS} import java.nio.file.{Files, Path}
import org.enso.runtimeversionmanager.FileSystem.PathSyntax import FileSystem.PathSyntax
import org.enso.runtimeversionmanager.test.WithTemporaryDirectory import org.enso.runtimeversionmanager.test.WithTemporaryDirectory
import org.enso.launcher._ import org.enso.launcher._

View File

@ -1,9 +1,9 @@
package org.enso.launcher.installation package org.enso.launcher.installation
import java.nio.file.{Files, Path} import org.enso.distribution.{FileSystem, OS}
import org.enso.runtimeversionmanager.{FileSystem, OS} import java.nio.file.{Files, Path}
import org.enso.runtimeversionmanager.FileSystem.PathSyntax import FileSystem.PathSyntax
import org.enso.runtimeversionmanager.test.WithTemporaryDirectory import org.enso.runtimeversionmanager.test.WithTemporaryDirectory
import org.enso.launcher.NativeTest import org.enso.launcher.NativeTest

View File

@ -1,8 +1,8 @@
package org.enso.launcher.project package org.enso.launcher.project
import nl.gn0s1s.bump.SemVer import nl.gn0s1s.bump.SemVer
import org.enso.distribution.DistributionManager
import org.enso.runtimeversionmanager.config.GlobalConfigurationManager import org.enso.runtimeversionmanager.config.GlobalConfigurationManager
import org.enso.runtimeversionmanager.distribution.DistributionManager
import org.enso.runtimeversionmanager.test.RuntimeVersionManagerTest import org.enso.runtimeversionmanager.test.RuntimeVersionManagerTest
import org.enso.pkg.Contact import org.enso.pkg.Contact
import org.scalatest.{Inside, OptionValues} import org.scalatest.{Inside, OptionValues}
@ -19,7 +19,7 @@ class ProjectManagerSpec
new GlobalConfigurationManager(null, distributionManager) { new GlobalConfigurationManager(null, distributionManager) {
override def defaultVersion: SemVer = defaultEnsoVersion override def defaultVersion: SemVer = defaultEnsoVersion
} }
(fakeConfigurationManager, new ProjectManager(fakeConfigurationManager)) (fakeConfigurationManager, new ProjectManager())
} }
"ProjectManager" should { "ProjectManager" should {
@ -43,7 +43,8 @@ class ProjectManagerSpec
projectDir.resolve("src").resolve("Main.enso").toFile should exist projectDir.resolve("src").resolve("Main.enso").toFile should exist
val project = projectManager.loadProject(projectDir).get val project = projectManager.loadProject(projectDir).get
project.version shouldEqual defaultEnsoVersion // TODO [RW] this test may change once we switch to deriving a particular edition by default
project.edition.engineVersion should contain(defaultEnsoVersion)
project.config.authors.headOption.value shouldEqual author project.config.authors.headOption.value shouldEqual author
project.config.maintainers.headOption.value shouldEqual author project.config.maintainers.headOption.value shouldEqual author
} }

View File

@ -1,8 +1,8 @@
package org.enso.launcher.releases.fallback package org.enso.launcher.releases.fallback
import java.nio.file.Path import org.enso.distribution.FileSystem
import org.enso.runtimeversionmanager.FileSystem import java.nio.file.Path
import org.enso.launcher.TestHelpers import org.enso.launcher.TestHelpers
import org.enso.launcher.releases.fallback.staticwebsite.FileStorageFallbackReleaseProvider import org.enso.launcher.releases.fallback.staticwebsite.FileStorageFallbackReleaseProvider
import org.scalatest.matchers.should.Matchers import org.scalatest.matchers.should.Matchers

View File

@ -1,9 +1,8 @@
package org.enso.launcher.releases.fallback package org.enso.launcher.releases.fallback
import java.nio.file.Path import java.nio.file.Path
import org.enso.cli.task.{TaskProgress, TaskProgressImplementation} import org.enso.cli.task.{TaskProgress, TaskProgressImplementation}
import org.enso.runtimeversionmanager.FileSystem import org.enso.distribution.FileSystem
import org.enso.launcher.TestHelpers import org.enso.launcher.TestHelpers
import org.enso.launcher.releases.fallback.staticwebsite.FileStorage import org.enso.launcher.releases.fallback.staticwebsite.FileStorage

View File

@ -1,13 +1,12 @@
package org.enso.launcher.upgrade package org.enso.launcher.upgrade
import java.nio.file.{Files, Path, StandardCopyOption} import java.nio.file.{Files, Path, StandardCopyOption}
import io.circe.parser import io.circe.parser
import nl.gn0s1s.bump.SemVer import nl.gn0s1s.bump.SemVer
import org.enso.runtimeversionmanager.{FileSystem, OS} import org.enso.distribution.{FileSystem, OS}
import org.enso.runtimeversionmanager.FileSystem.PathSyntax import org.enso.distribution.locking.{FileLockManager, LockType}
import FileSystem.PathSyntax
import org.enso.launcher._ import org.enso.launcher._
import org.enso.runtimeversionmanager.locking.{FileLockManager, LockType}
import org.enso.runtimeversionmanager.test.WithTemporaryDirectory import org.enso.runtimeversionmanager.test.WithTemporaryDirectory
import org.enso.testkit.RetrySpec import org.enso.testkit.RetrySpec
import org.scalatest.exceptions.TestFailedException import org.scalatest.exceptions.TestFailedException

View File

@ -4,7 +4,7 @@ import org.enso.polyglot.debugger.protocol.{
ExceptionRepresentation, ExceptionRepresentation,
ObjectRepresentation ObjectRepresentation
} }
import org.enso.testkit.EitherValue
import org.scalatest.matchers.should.Matchers import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AnyWordSpec import org.scalatest.wordspec.AnyWordSpec

View File

@ -3,10 +3,11 @@ package org.enso.runner
import akka.http.scaladsl.model.{IllegalUriException, Uri} import akka.http.scaladsl.model.{IllegalUriException, Uri}
import cats.implicits._ import cats.implicits._
import org.apache.commons.cli.{Option => CliOption, _} import org.apache.commons.cli.{Option => CliOption, _}
import org.enso.editions.SemVerEnsoVersion
import org.enso.languageserver.boot import org.enso.languageserver.boot
import org.enso.languageserver.boot.LanguageServerConfig import org.enso.languageserver.boot.LanguageServerConfig
import org.enso.loggingservice.LogLevel import org.enso.loggingservice.LogLevel
import org.enso.pkg.{Contact, PackageManager, SemVerEnsoVersion} import org.enso.pkg.{Contact, PackageManager}
import org.enso.polyglot.{LanguageInfo, Module, PolyglotContext} import org.enso.polyglot.{LanguageInfo, Module, PolyglotContext}
import org.enso.version.VersionDescription import org.enso.version.VersionDescription
import org.graalvm.polyglot.PolyglotException import org.graalvm.polyglot.PolyglotException

View File

@ -1,11 +1,9 @@
package org.enso.runtimeversionmanager.distribution package org.enso.distribution
import java.nio.file.{Files, Path}
import com.typesafe.scalalogging.Logger import com.typesafe.scalalogging.Logger
import org.enso.runtimeversionmanager.FileSystem.PathSyntax import org.enso.distribution.FileSystem.PathSyntax
import org.enso.runtimeversionmanager.{Environment, FileSystem, OS}
import java.nio.file.{Files, Path}
import scala.util.Try import scala.util.Try
import scala.util.control.NonFatal import scala.util.control.NonFatal
@ -27,6 +25,10 @@ import scala.util.control.NonFatal
* @param unsafeTemporaryDirectory path to the temporary directory, should not * @param unsafeTemporaryDirectory path to the temporary directory, should not
* be used directly, see * be used directly, see
* [[TemporaryDirectoryManager]] * [[TemporaryDirectoryManager]]
* @param customEditions the search paths for editions
* @param localLibrariesSearchPaths a sequence of paths to search for local
* libraries, in order of precedence
* @param ensoHome the home directory for user's projects etc.
*/ */
case class DistributionPaths( case class DistributionPaths(
dataRoot: Path, dataRoot: Path,
@ -36,7 +38,10 @@ case class DistributionPaths(
config: Path, config: Path,
locks: Path, locks: Path,
logs: Path, logs: Path,
unsafeTemporaryDirectory: Path unsafeTemporaryDirectory: Path,
customEditions: Seq[Path],
localLibrariesSearchPaths: Seq[Path],
ensoHome: Path
) { ) {
/** @inheritdoc */ /** @inheritdoc */
@ -48,7 +53,8 @@ case class DistributionPaths(
| bundle = $bundle, | bundle = $bundle,
| config = $config, | config = $config,
| locks = $locks, | locks = $locks,
| tmp = $unsafeTemporaryDirectory | tmp = $unsafeTemporaryDirectory,
| ensoHome = $ensoHome
|)""".stripMargin |)""".stripMargin
/** Sequence of paths to search for engine installations, in order of /** Sequence of paths to search for engine installations, in order of
@ -61,6 +67,18 @@ case class DistributionPaths(
*/ */
def runtimeSearchPaths: Seq[Path] = def runtimeSearchPaths: Seq[Path] =
Seq(runtimes) ++ bundle.map(_.runtimes).toSeq Seq(runtimes) ++ bundle.map(_.runtimes).toSeq
/** The directory for cached editions managed by us. */
def cachedEditions: Path = dataRoot / DistributionManager.EDITIONS_DIRECTORY
/** The directory for cached libraries managed by us. */
def cachedLibraries: Path = dataRoot / DistributionManager.LIBRARIES_DIRECTORY
/** Sequence of paths to search for edition configurations, in order of
* precedence.
*/
def editionSearchPaths: Seq[Path] =
customEditions ++ Seq(cachedEditions)
} }
/** Paths to secondary directories for additionally bundled engine /** Paths to secondary directories for additionally bundled engine
@ -84,6 +102,7 @@ case class Bundle(engines: Path, runtimes: Path)
*/ */
class DistributionManager(val env: Environment) { class DistributionManager(val env: Environment) {
private val logger = Logger[DistributionManager] private val logger = Logger[DistributionManager]
import DistributionManager._
/** Determines paths that should be used by the launcher. /** Determines paths that should be used by the launcher.
*/ */
@ -93,30 +112,56 @@ class DistributionManager(val env: Environment) {
paths paths
} }
val ENGINES_DIRECTORY = "dist"
val RUNTIMES_DIRECTORY = "runtime"
val CONFIG_DIRECTORY = "config"
val BIN_DIRECTORY = "bin"
val LOCK_DIRECTORY = "lock"
val LOG_DIRECTORY = "log"
val TMP_DIRECTORY = "tmp"
protected def detectPaths(): DistributionPaths = { protected def detectPaths(): DistributionPaths = {
val dataRoot = LocallyInstalledDirectories.dataDirectory val dataRoot = LocallyInstalledDirectories.dataDirectory
val configRoot = LocallyInstalledDirectories.configDirectory val configRoot = LocallyInstalledDirectories.configDirectory
val runRoot = LocallyInstalledDirectories.runtimeDirectory val runRoot = LocallyInstalledDirectories.runtimeDirectory
val home = detectEnsoHome()
DistributionPaths( DistributionPaths(
dataRoot = dataRoot, dataRoot = dataRoot,
runtimes = dataRoot / RUNTIMES_DIRECTORY, runtimes = dataRoot / RUNTIMES_DIRECTORY,
engines = dataRoot / ENGINES_DIRECTORY, engines = dataRoot / ENGINES_DIRECTORY,
bundle = detectBundle(), bundle = detectBundle(),
config = configRoot, config = configRoot,
locks = runRoot / LOCK_DIRECTORY, locks = runRoot / LOCK_DIRECTORY,
logs = LocallyInstalledDirectories.logDirectory, logs = LocallyInstalledDirectories.logDirectory,
unsafeTemporaryDirectory = dataRoot / TMP_DIRECTORY unsafeTemporaryDirectory = dataRoot / TMP_DIRECTORY,
customEditions = detectCustomEditionPaths(home),
localLibrariesSearchPaths = detectLocalLibraryPaths(home),
ensoHome = home
) )
} }
private val ENSO_HOME = "ENSO_HOME"
private val ENSO_EDITION_PATH = "ENSO_EDITION_PATH"
private val ENSO_LIBRARY_PATH = "ENSO_LIBRARY_PATH"
/** Finds the path to the ENSO_HOME directory that is used for keeping user's
* projects, libraries and other custom artifacts.
*/
protected def detectEnsoHome(): Path =
env.getEnvPath(ENSO_HOME).getOrElse(env.getUserProfile / "enso")
/** Finds the paths to look for custom editions, which may be overridden by
* setting the ENSO_EDITION_PATH environment variable.
*/
protected def detectCustomEditionPaths(ensoHome: Path): Seq[Path] =
env
.getEnvPaths(ENSO_EDITION_PATH)
.getOrElse {
Seq(ensoHome / DistributionManager.Home.EDITIONS_DIRECTORY)
}
/** Finds the paths to look for local libraries, which may be overridden by
* setting the ENSO_LIBRARY_PATH environment variable.
*/
protected def detectLocalLibraryPaths(ensoHome: Path): Seq[Path] =
env
.getEnvPaths(ENSO_LIBRARY_PATH)
.getOrElse {
Seq(ensoHome / DistributionManager.Home.LIBRARIES_DIRECTORY)
}
/** Name of the file that should be placed in the distribution root to mark it /** Name of the file that should be placed in the distribution root to mark it
* as running in portable mode. * as running in portable mode.
*/ */
@ -315,3 +360,24 @@ class DistributionManager(val env: Environment) {
safeDataDirectory.exists(Files.isDirectory(_)) safeDataDirectory.exists(Files.isDirectory(_))
} }
} }
/** A helper object that contains constants defining names of various
* directories used by Enso components.
*/
object DistributionManager {
val ENGINES_DIRECTORY = "dist"
val RUNTIMES_DIRECTORY = "runtime"
val CONFIG_DIRECTORY = "config"
val BIN_DIRECTORY = "bin"
val LOCK_DIRECTORY = "lock"
val LOG_DIRECTORY = "log"
val TMP_DIRECTORY = "tmp"
val EDITIONS_DIRECTORY = "editions"
val LIBRARIES_DIRECTORY = "lib"
/** Defines paths inside of the ENSO_HOME directory. */
object Home {
val EDITIONS_DIRECTORY = "editions"
val LIBRARIES_DIRECTORY = "libraries"
}
}

View File

@ -0,0 +1,37 @@
package org.enso.distribution
import org.enso.editions
import org.enso.editions.provider.FileSystemEditionProvider
import org.enso.editions.{EditionResolver, Editions, EnsoVersion}
import scala.util.Try
/** A helper class for resolving editions backed by the Edition storage managed
* by the DistributionManager.
*/
case class EditionManager(distributionManager: DistributionManager) {
private val editionProvider = FileSystemEditionProvider(
distributionManager.paths.editionSearchPaths.toList
)
private val editionResolver = EditionResolver(editionProvider)
private val engineVersionResolver =
editions.EngineVersionResolver(editionProvider)
/** Resolves a raw edition, loading its parents from the edition search path.
* @param edition the edition to resolve
* @return the resolved edition
*/
def resolveEdition(
edition: Editions.RawEdition
): Try[Editions.ResolvedEdition] =
editionResolver.resolve(edition).toTry
/** Resolves the engine version that should be used based on the provided raw
* edition configuration.
* @param edition the edition configuration to base the selected version on
* @return the resolved engine version
*/
def resolveEngineVersion(edition: Editions.RawEdition): Try[EnsoVersion] =
engineVersionResolver.resolveEnsoVersion(edition).toTry
}

View File

@ -1,11 +1,10 @@
package org.enso.runtimeversionmanager package org.enso.distribution
import java.io.File
import java.nio.file.Path
import com.typesafe.scalalogging.Logger import com.typesafe.scalalogging.Logger
import org.enso.logger.masking.MaskedString import org.enso.logger.masking.MaskedString
import java.io.File
import java.nio.file.Path
import scala.util.Try import scala.util.Try
/** Gathers some helper methods querying the system environment. /** Gathers some helper methods querying the system environment.
@ -39,26 +38,38 @@ trait Environment {
* *
* If it is not defined or is not a valid path, returns None. * If it is not defined or is not a valid path, returns None.
*/ */
def getEnvPath(key: String): Option[Path] = { def getEnvPath(key: String): Option[Path] =
def parsePathWithWarning(str: String): Option[Path] = { getEnvVar(key).flatMap(parsePathWithWarning(key))
val result = safeParsePath(str)
if (result.isEmpty) { /** Queries the system environment for the given variable that should
Logger[Environment].warn( * represent a list of valid filesystem paths.
"System variable [{}] was set to [{}], but it did not " + *
"represent a valid path, so it has been ignored.", * If a path is on the list but is invalid, it is skipped.
key, */
MaskedString(str) def getEnvPaths(key: String): Option[Seq[Path]] =
) getEnvVar(key)
.map { value =>
value
.split(File.pathSeparator)
.toSeq
.flatMap(str => parsePathWithWarning(key)(str).toSeq)
} }
result private def parsePathWithWarning(key: String)(str: String): Option[Path] = {
val result = safeParsePath(str)
if (result.isEmpty) {
Logger[Environment].warn(
"System variable [{}] was set to [{}], but it did not " +
"represent a valid path, so it has been ignored.",
key,
MaskedString(str)
)
} }
getEnvVar(key).flatMap(parsePathWithWarning) result
} }
/** Returns the system PATH, if available. /** Returns the system PATH, if available. */
*/
def getSystemPath: Seq[Path] = def getSystemPath: Seq[Path] =
getEnvVar("PATH") getEnvVar("PATH")
.map(_.split(File.pathSeparatorChar).toSeq.flatMap(safeParsePath)) .map(_.split(File.pathSeparatorChar).toSeq.flatMap(safeParsePath))
@ -85,6 +96,33 @@ trait Environment {
} }
} }
/** Returns the location of the user profile directory (`%UserProfile%`) on
* Windows.
*/
def getWindowsUserProfile: Path = {
if (!OS.isWindows)
throw new IllegalStateException(
"fatal error: USERPROFILE should be queried only on Windows."
)
else {
getEnvVar("USERPROFILE").flatMap(safeParsePath) match {
case Some(path) => path
case None =>
throw new RuntimeException(
"fatal error: %USERPROFILE% environment variable is not defined."
)
}
}
}
/** Returns a path to the user's home directory, as defined on their
* particular Operating System.
*
* On UNIX, this is $HOME and on Windows it is %UserProfile%.
*/
def getUserProfile: Path =
if (OS.isWindows) getWindowsUserProfile else getHome
/** Returns the location of the local application data directory /** Returns the location of the local application data directory
* (`%LocalAppData%`) on Windows. * (`%LocalAppData%`) on Windows.
* *

View File

@ -1,4 +1,7 @@
package org.enso.runtimeversionmanager package org.enso.distribution
import com.typesafe.scalalogging.Logger
import org.apache.commons.io.FileUtils
import java.io.PrintWriter import java.io.PrintWriter
import java.nio.file.attribute.PosixFilePermissions import java.nio.file.attribute.PosixFilePermissions
@ -8,10 +11,6 @@ import java.nio.file.{
Path, Path,
StandardCopyOption StandardCopyOption
} }
import com.typesafe.scalalogging.Logger
import org.apache.commons.io.FileUtils
import scala.collection.Factory import scala.collection.Factory
import scala.jdk.StreamConverters._ import scala.jdk.StreamConverters._
import scala.util.Using import scala.util.Using

View File

@ -1,4 +1,4 @@
package org.enso.runtimeversionmanager package org.enso.distribution
import com.typesafe.scalalogging.Logger import com.typesafe.scalalogging.Logger
import io.circe.{Decoder, DecodingFailure} import io.circe.{Decoder, DecodingFailure}

View File

@ -1,10 +1,9 @@
package org.enso.runtimeversionmanager.distribution package org.enso.distribution
import java.nio.file.{Files, Path}
import com.typesafe.scalalogging.Logger import com.typesafe.scalalogging.Logger
import org.enso.runtimeversionmanager.Environment import org.enso.distribution.FileSystem.PathSyntax
import org.enso.runtimeversionmanager.FileSystem.PathSyntax
import java.nio.file.{Files, Path}
/** A specialized variant of [[DistributionManager]] that is able to detect if /** A specialized variant of [[DistributionManager]] that is able to detect if
* the currently running distribution is running in portable or locally * the currently running distribution is running in portable or locally
@ -69,15 +68,20 @@ class PortableDistributionManager(env: Environment)
override protected def detectPaths(): DistributionPaths = override protected def detectPaths(): DistributionPaths =
if (isRunningPortable) { if (isRunningPortable) {
val root = env.getPathToRunningExecutable.getParent.getParent val root = env.getPathToRunningExecutable.getParent.getParent
val home = detectEnsoHome()
import DistributionManager._
DistributionPaths( DistributionPaths(
dataRoot = root, dataRoot = root,
runtimes = root / RUNTIMES_DIRECTORY, runtimes = root / RUNTIMES_DIRECTORY,
engines = root / ENGINES_DIRECTORY, engines = root / ENGINES_DIRECTORY,
bundle = None, bundle = None,
config = root / CONFIG_DIRECTORY, config = root / CONFIG_DIRECTORY,
locks = root / LOCK_DIRECTORY, locks = root / LOCK_DIRECTORY,
logs = root / LOG_DIRECTORY, logs = root / LOG_DIRECTORY,
unsafeTemporaryDirectory = root / TMP_DIRECTORY unsafeTemporaryDirectory = root / TMP_DIRECTORY,
customEditions = detectCustomEditionPaths(home),
localLibrariesSearchPaths = detectLocalLibraryPaths(home),
ensoHome = home
) )
} else super.detectPaths() } else super.detectPaths()

View File

@ -1,4 +1,4 @@
package org.enso.runtimeversionmanager.locking package org.enso.distribution.locking
import java.nio.channels.{FileChannel, FileLock} import java.nio.channels.{FileChannel, FileLock}
import java.nio.file.{Files, Path, StandardOpenOption} import java.nio.file.{Files, Path, StandardOpenOption}

View File

@ -1,4 +1,4 @@
package org.enso.runtimeversionmanager.locking package org.enso.distribution.locking
import scala.util.Using.Releasable import scala.util.Using.Releasable

View File

@ -1,4 +1,4 @@
package org.enso.runtimeversionmanager.locking package org.enso.distribution.locking
/** Manages locks that can be used to synchronize different launcher processes /** Manages locks that can be used to synchronize different launcher processes
* running in parallel to avoid components corruption caused by simultaneous * running in parallel to avoid components corruption caused by simultaneous

View File

@ -1,4 +1,4 @@
package org.enso.runtimeversionmanager.locking package org.enso.distribution.locking
/** Defines the lock type. /** Defines the lock type.
*/ */

View File

@ -1,4 +1,4 @@
package org.enso.runtimeversionmanager.locking package org.enso.distribution.locking
/** Defines callbacks that can be called when a requested resource is locked and /** Defines callbacks that can be called when a requested resource is locked and
* the application has to wait for other processes. * the application has to wait for other processes.

View File

@ -0,0 +1,16 @@
package org.enso.distribution.locking
/** Represents a resource that can be locked. */
trait Resource {
/** Name of the resource.
*
* Must be a valid filename part.
*/
def name: String
/** A message that is displayed by default if the lock on that resource cannot
* be acquired immediately.
*/
def waitMessage: String
}

View File

@ -1,4 +1,4 @@
package org.enso.runtimeversionmanager.locking package org.enso.distribution.locking
import com.typesafe.scalalogging.Logger import com.typesafe.scalalogging.Logger

View File

@ -1,4 +1,4 @@
package org.enso.runtimeversionmanager.locking package org.enso.distribution.locking
import java.nio.file.Path import java.nio.file.Path

View File

@ -0,0 +1,65 @@
package org.enso.editions
import cats.Show
/** Indicates an error during resolution of a raw edition. */
sealed class EditionResolutionError(message: String, cause: Throwable = null)
extends RuntimeException(message, cause)
object EditionResolutionError {
/** Indicates that a parent edition referenced in one of the editions that are
* being loaded cannot be loaded.
*/
case class CannotLoadEdition(name: String, cause: Throwable)
extends EditionResolutionError(
s"Cannot load the edition: ${cause.getMessage}",
cause
)
/** Indicates that the edition cannot be parsed. */
case class EditionParseError(message: String, cause: Throwable)
extends EditionResolutionError(message, cause)
/** Indicates that a library defined in an edition references a repository
* that is not defined in that edition or any of its parents, and so such a
* reference is invalid.
*/
case class LibraryReferencesUndefinedRepository(
libraryName: String,
repositoryName: String
) extends EditionResolutionError(
s"A library `$libraryName` references a repository `$repositoryName` " +
s"that is not defined in the edition or its parents."
)
/** Indicates that the chain of parent editions forms a cycle which is not
* allowed.
*/
case class EditionResolutionCycle(editions: List[String])
extends EditionResolutionError(
s"Edition resolution encountered a cycle: ${editions.mkString(" -> ")}"
)
/** Wraps a Circe's decoding error into a more user-friendly error. */
def wrapDecodingError(decodingError: io.circe.Error): EditionParseError = {
val errorMessage =
implicitly[Show[io.circe.Error]].show(decodingError)
EditionParseError(
s"Could not parse the edition: $errorMessage",
decodingError
)
}
/** Wraps a general error thrown when loading a parsing an edition into a more
* specific error type.
*/
def wrapLoadingError(
editionName: String,
throwable: Throwable
): EditionResolutionError =
throwable match {
case decodingError: io.circe.Error => wrapDecodingError(decodingError)
case other => CannotLoadEdition(editionName, other)
}
}

View File

@ -0,0 +1,155 @@
package org.enso.editions
import cats.implicits._
import org.enso.editions.EditionResolutionError.{
EditionResolutionCycle,
LibraryReferencesUndefinedRepository
}
import org.enso.editions.Editions.{RawEdition, ResolvedEdition}
import org.enso.editions.provider.EditionProvider
import scala.annotation.tailrec
/** A helper class that allows to resolve a [[RawEdition]] into a
* [[ResolvedEdition]] by loading its parents and resolving the repository
* references.
*
* @param provider an [[EditionProvider]] that is used for loading the
* referenced parent editions
*/
case class EditionResolver(provider: EditionProvider) {
/** Runs the edition resolution.
*
* @param edition the raw edition to resolve
* @return either a resolution error or the resolved edition
*/
def resolve(
edition: RawEdition
): Either[EditionResolutionError, ResolvedEdition] =
resolveEdition(edition, Nil)
/** A helper method that resolves an edition and keeps a list of already
* visited edition names to avoid cycles.
*/
private def resolveEdition(
edition: RawEdition,
visitedEditions: List[String]
): Either[EditionResolutionError, ResolvedEdition] =
for {
parent <- resolveParent(edition.parent, visitedEditions)
libraries <- resolveLibraries(
edition.libraries,
edition.repositories,
parent
)
} yield Editions.Resolved.Edition(
parent = parent,
engineVersion = edition.engineVersion,
repositories = edition.repositories,
libraries = libraries
)
/** A helper method for resolving libraries.
*
* @param libraries the raw mapping of libraries
* @param currentRepositories the mapping of repositories defined in the
* current edition
* @param parent an optional reference to an (already resolved) parent
* edition, which is used if the library references a
* repository that was not defined in `currentRepositories`
* @return either an error indicating a reference to an unknown repository or
* a mapping of resolved libraries
*/
private def resolveLibraries(
libraries: Map[String, Editions.Raw.Library],
currentRepositories: Map[String, Editions.Repository],
parent: Option[ResolvedEdition]
): Either[
LibraryReferencesUndefinedRepository,
Map[String, Editions.Resolved.Library]
] = {
val resolvedPairs: Either[
LibraryReferencesUndefinedRepository,
List[(String, Editions.Resolved.Library)]
] =
libraries.toList.traverse { case (name, library) =>
val resolved = resolveLibrary(library, currentRepositories, parent)
resolved.map((name, _))
}
resolvedPairs.map(Map.from)
}
/** A helper method to resolve a single library.
*
* @param library the library to resolve
* @param currentRepositories the mapping of repositories defined in the
* current edition
* @param parent an optional reference to an (already resolved) parent
* edition, which is used if the library references a
* repository that was not defined in `currentRepositories`
* @return either an error or the resolved library
*/
@tailrec
private def resolveLibrary(
library: Editions.Raw.Library,
currentRepositories: Map[String, Editions.Repository],
parent: Option[ResolvedEdition]
): Either[
LibraryReferencesUndefinedRepository,
Editions.Resolved.Library
] = library match {
case Editions.Raw.LocalLibrary(qualifiedName) =>
Right(Editions.Resolved.LocalLibrary(qualifiedName))
case Editions.Raw.PublishedLibrary(
qualifiedName,
version,
repositoryName
) =>
(currentRepositories.get(repositoryName), parent) match {
case (Some(repository), _) =>
Right(
Editions.Resolved
.PublishedLibrary(qualifiedName, version, repository)
)
case (None, Some(parentEdition)) =>
resolveLibrary(
library,
parentEdition.repositories,
parentEdition.parent
)
case (None, None) =>
Left(
LibraryReferencesUndefinedRepository(
libraryName = library.qualifiedName,
repositoryName = repositoryName
)
)
}
}
/** A helper function that takes an optional parent name and list of already
* seen editions and tries to load the parent (if present), but avoiding
* cycles (returning an error if a cycle was encountered).
*/
private def resolveParent(
parent: Option[String],
visitedEditions: List[String]
): Either[EditionResolutionError, Option[ResolvedEdition]] = parent match {
case Some(parentName) =>
if (visitedEditions.contains(parentName)) {
val cycle = parentName :: visitedEditions
Left(EditionResolutionCycle(cycle.reverse))
} else
for {
rawParent <- provider
.findEditionForName(parentName)
.toEither
.left
.map(EditionResolutionError.CannotLoadEdition(parentName, _))
res <- resolveEdition(rawParent, parentName :: visitedEditions)
} yield Some(res)
case None => Right(None)
}
}

View File

@ -0,0 +1,259 @@
package org.enso.editions
import cats.Show
import io.circe.syntax.EncoderOps
import io.circe._
import nl.gn0s1s.bump.SemVer
import org.enso.editions.Editions.{Raw, Repository}
import org.enso.editions.SemVerJson._
import java.io.FileReader
import java.net.URL
import java.nio.file.Path
import scala.util.{Failure, Try, Using}
/** Gathers methods for decoding and encoding of Raw editions. */
object EditionSerialization {
/** Tries to parse an edition definition from a string in the YAML format. */
def parseYamlString(yamlString: String): Try[Raw.Edition] =
yaml.parser
.parse(yamlString)
.flatMap(_.as[Raw.Edition])
.left
.map(EditionResolutionError.wrapDecodingError)
.toTry
/** Tries to load an edition definition from a YAML file. */
def loadEdition(path: Path): Try[Raw.Edition] =
Using(new FileReader(path.toFile)) { reader =>
yaml.parser
.parse(reader)
.flatMap(_.as[Raw.Edition])
.toTry
}.flatten
.recoverWith { error =>
Failure(
EditionResolutionError.wrapLoadingError(
path.getFileName.toString,
error
)
)
}
/** A [[Decoder]] instance for [[Raw.Edition]].
*
* It can be used to decode nested editions in other kinds of configurations
* files, for example in `package.yaml`.
*/
implicit val editionDecoder: Decoder[Raw.Edition] = { json =>
for {
parent <- json.get[Option[EditionName]](Fields.parent)
engineVersion <- json.get[Option[EnsoVersion]](Fields.engineVersion)
_ <-
if (parent.isEmpty && engineVersion.isEmpty)
Left(
DecodingFailure(
s"The edition must specify at least one of " +
s"${Fields.engineVersion} or ${Fields.parent}.",
json.history
)
)
else Right(())
repositories <-
json.getOrElse[Seq[Repository]](Fields.repositories)(Seq())
libraries <- json.getOrElse[Seq[Raw.Library]](Fields.libraries)(Seq())
res <- {
val repositoryMap = Map.from(repositories.map(r => (r.name, r)))
val libraryMap = Map.from(libraries.map(l => (l.qualifiedName, l)))
if (libraryMap.size != libraries.size)
Left(
DecodingFailure(
"Names of libraries defined within a single edition file must be unique.",
json.downField(Fields.libraries).history
)
)
else if (repositoryMap.size != repositories.size)
Left(
DecodingFailure(
"Names of repositories defined within a single edition file must be unique.",
json.downField(Fields.libraries).history
)
)
else
Right(
Raw.Edition(
parent = parent.map(_.name),
engineVersion = engineVersion,
repositories = repositoryMap,
libraries = libraryMap
)
)
}
} yield res
}
/** An [[Encoder]] instance for [[Raw.Edition]]. */
implicit val editionEncoder: Encoder[Raw.Edition] = { edition =>
val parent = edition.parent.map { parent => Fields.parent -> parent.asJson }
val engineVersion = edition.engineVersion.map { version =>
Fields.engineVersion -> version.asJson
}
if (parent.isEmpty && engineVersion.isEmpty) {
throw new IllegalArgumentException(
"Internal error: An edition must specify at least the engine version or extends clause"
)
}
val repositories =
if (edition.repositories.isEmpty) None
else Some(Fields.repositories -> edition.repositories.values.asJson)
val libraries =
if (edition.libraries.isEmpty) None
else Some(Fields.libraries -> edition.libraries.values.asJson)
Json.obj(
parent.toSeq ++ engineVersion.toSeq ++ repositories.toSeq ++ libraries.toSeq: _*
)
}
object Fields {
val name = "name"
val version = "version"
val repository = "repository"
val url = "url"
val parent = "extends"
val engineVersion = "engine-version"
val repositories = "repositories"
val libraries = "libraries"
val localRepositoryName = "local"
}
case class EditionLoadingError(message: String, cause: Throwable)
extends RuntimeException(message, cause) {
/** @inheritdoc */
override def toString: String = message
}
object EditionLoadingError {
/** Creates a [[EditionLoadingError]] by wrapping another [[Throwable]].
*
* Special logic is used for [[io.circe.Error]] to display the error
* summary in a human-readable way.
*/
def fromThrowable(throwable: Throwable): EditionLoadingError =
throwable match {
case decodingError: io.circe.Error =>
val errorMessage =
implicitly[Show[io.circe.Error]].show(decodingError)
EditionLoadingError(
s"Could not parse the edition file: $errorMessage",
decodingError
)
case other =>
EditionLoadingError(s"Could not load the edition file: $other", other)
}
}
/** A helper opaque type to handle special parsing logic of edition names.
*
* The issue is that if an edition is called `2021.4` and it is written
* unquoted inside of a YAML file, that is treated as a floating point
* number, so special care must be taken to correctly parse it.
*/
private case class EditionName(name: String)
implicit private val editionNameDecoder: Decoder[EditionName] = { json =>
json
.as[String]
.orElse(json.as[Int].map(_.toString))
.orElse(json.as[Float].map(_.toString))
.map(EditionName)
}
implicit private val libraryDecoder: Decoder[Raw.Library] = { json =>
def makeLibrary(name: String, repository: String, version: Option[SemVer]) =
if (repository == Fields.localRepositoryName)
if (version.isDefined)
Left(
DecodingFailure(
"Version field must not be set for libraries associated with the local repository.",
json.history
)
)
else Right(Raw.LocalLibrary(name))
else {
version match {
case Some(versionValue) =>
Right(Raw.PublishedLibrary(name, versionValue, repository))
case None =>
Left(
DecodingFailure(
"Version field is mandatory for non-local libraries.",
json.history
)
)
}
}
for {
name <- json.get[String](Fields.name)
repository <- json.get[String](Fields.repository)
version <- json.get[Option[SemVer]](Fields.version)
res <- makeLibrary(name, repository, version)
} yield res
}
implicit private val libraryEncoder: Encoder[Raw.Library] = {
case Raw.LocalLibrary(name) =>
Json.obj(
Fields.name -> name.asJson,
Fields.repository -> Fields.localRepositoryName.asJson
)
case Raw.PublishedLibrary(name, version, repository) =>
Json.obj(
Fields.name -> name.asJson,
Fields.version -> version.asJson,
Fields.repository -> repository.asJson
)
}
implicit private val urlEncoder: Encoder[URL] = { url => url.toString.asJson }
implicit private val urlDecoder: Decoder[URL] = { json =>
json.as[String].flatMap { str =>
Try(new URL(str)).toEither.left.map(throwable =>
DecodingFailure(
s"Cannot parse an URL: ${throwable.getMessage}",
json.history
)
)
}
}
implicit private val repositoryEncoder: Encoder[Repository] = { repo =>
Json.obj(
Fields.name -> repo.name.asJson,
Fields.url -> repo.url.asJson
)
}
implicit private val repositoryDecoder: Decoder[Repository] = { json =>
val nameField = json.downField(Fields.name)
for {
name <- nameField.as[String]
url <- json.get[URL](Fields.url)
res <-
if (name == Fields.localRepositoryName)
Left(
DecodingFailure(
s"A defined repository cannot be called " +
s"`${Fields.localRepositoryName}` which is a reserved keyword.",
nameField.history
)
)
else Right(Repository(name, url))
} yield res
}
}

View File

@ -0,0 +1,147 @@
package org.enso.editions
import nl.gn0s1s.bump.SemVer
import java.net.URL
import scala.util.Try
/** Defines the general edition structure.
*
* We split the data type into two categories: Raw and Resolved editions.
*
* Raw editions are directly parsed from a YAML structure and can be serialized
* back to it. They are just an object model of the underlying YAML format.
*
* The raw edition may reference a parent edition or repositories by name.
* The [[EditionResolver]] takes care of resolving a Raw edition into a
* Resolved instance by loading and parsing any of its parents and replacing
* the repository by-name references by actual references to the repository
* instances.
*/
trait Editions {
/** The type of nested editions.
*
* Raw editions will refer to parents by their name and the Resolved edition
* will contain a reference to an actual object.
*/
type NestedEditionType
/** The type of repository reference in listed libraries.
*
* Libraries in Raw editions will refer to repositories by their name and the
* Resolved variant will contain a reference to the actual repository object
* loaded from the edition or one of its parents.
*/
type LibraryRepositoryType
/** The library description included in the edition. */
sealed trait Library {
/** The qualified name of the library.
*
* It should consist of a prefix followed by a dot an the library name, for
* example `Prefix.Library_Name`.
*/
def qualifiedName: String
}
/** Represents a local library. */
case class LocalLibrary(override val qualifiedName: String) extends Library
/** Represents a specific version of the library that is published in a
* repository.
*
* @param qualifiedName the qualified name of the library
* @param version the exact version of the library that should be used
* @param repository the recommended repository to download the library from,
* if it is not yet cached
*/
case class PublishedLibrary(
override val qualifiedName: String,
version: SemVer,
repository: LibraryRepositoryType
) extends Library
/** An Edition describing the library resolution configuration.
*
* @param parent a parent edition (if applicable)
* @param engineVersion an engine version; it should be defined if the
* edition wants to override the setting from the parent
* or if it has no parents
* @param repositories a mapping of repositories directly defined in the
* edition (does not include ones defined in the parents)
* @param libraries a mapping of libraries directly defined in the edition
* (does not include ones defined in the parents)
*/
case class Edition(
parent: Option[NestedEditionType],
engineVersion: Option[EnsoVersion],
repositories: Map[String, Editions.Repository],
libraries: Map[String, Library]
) {
if (parent.isEmpty && engineVersion.isEmpty)
throw new IllegalArgumentException(
"The edition must specify the engine version or a parent edition " +
"that will imply it."
)
}
}
object Editions {
/** Represents a repository that provides libraries. */
case class Repository(name: String, url: URL)
object Repository {
/** A helper function that creates a Repository instance from a raw string
* URL.
*/
def make(name: String, url: String): Try[Repository] = Try {
Repository(name, new URL(url))
}
}
/** Implements the Raw editions that can be directly parsed from a YAML
* configuration.
*
* All references are typed as String, because this is what is directly read
* from the configuration, without any postprocessing.
*/
object Raw extends Editions {
override type NestedEditionType = String
override type LibraryRepositoryType = String
}
/** Implements the Resolved editions which are obtained by analyzing the Raw
* edition and loading any of its parents.
*/
object Resolved extends Editions {
override type NestedEditionType = this.Edition
override type LibraryRepositoryType = Repository
}
/** An alias for Raw editions. */
type RawEdition = Raw.Edition
/** An alias for Resolved editions. */
type ResolvedEdition = Resolved.Edition
/** Syntax helpers for resolved edition. */
implicit class ResolvedEditionOps(edition: ResolvedEdition) {
/** Resolves the engine version that is implied by this resolved edition. It
* is either the version override directly specified in the edition or the
* version implied by its parent.
*/
def getEngineVersion: EnsoVersion = edition.engineVersion.getOrElse {
val parent = edition.parent.getOrElse {
throw new IllegalStateException(
"Internal error: Resolved edition does not imply an engine version."
)
}
parent.getEngineVersion
}
}
}

View File

@ -0,0 +1,27 @@
package org.enso.editions
import org.enso.editions.Editions.RawEdition
import org.enso.editions.provider.EditionProvider
/** A helper class which resolves the engine version that is entailed by the
* edition configuration.
*
* It requires an [[EditionProvider]] instance, because the edition may not
* specify the engine version directly, but it can just rely on what is
* specified in a parent edition, so it needs a way to load the parent
* editions.
*/
case class EngineVersionResolver(editionProvider: EditionProvider) {
private val editionResolver = EditionResolver(editionProvider)
/** Returns the [[EnsoVersion]] that is entailed by the provided edition (or a
* resolution error if the edition cannot be resolved).
*/
def resolveEnsoVersion(
edition: RawEdition
): Either[EditionResolutionError, EnsoVersion] = {
for {
edition <- editionResolver.resolve(edition)
} yield edition.getEngineVersion
}
}

View File

@ -1,4 +1,4 @@
package org.enso.pkg package org.enso.editions
import io.circe.syntax._ import io.circe.syntax._
import io.circe.{Decoder, DecodingFailure, Encoder} import io.circe.{Decoder, DecodingFailure, Encoder}

View File

@ -0,0 +1,16 @@
package org.enso.editions
/** Represents a library name that should uniquely identify the library.
*
* The prefix is either a special prefix or a username.
*/
case class LibraryName(prefix: String, name: String) {
/** The qualified name of the library consists of its prefix and name
* separated with a dot.
*/
def qualifiedName: String = s"$prefix.$name"
/** @inheritdoc */
override def toString: String = qualifiedName
}

View File

@ -1,7 +1,7 @@
package org.enso.pkg package org.enso.editions
import io.circe.{Decoder, DecodingFailure, Encoder}
import io.circe.syntax._ import io.circe.syntax._
import io.circe.{Decoder, DecodingFailure, Encoder}
import nl.gn0s1s.bump.SemVer import nl.gn0s1s.bump.SemVer
object SemVerJson { object SemVerJson {

View File

@ -0,0 +1,16 @@
package org.enso.editions.provider
import org.enso.editions.Editions
import scala.util.Try
/** Interface for a provider of editions which is able to load a raw edition
* based on its name.
*
* It is used when resolving parent edition references.
*/
trait EditionProvider {
/** Tries to load an edition with the given name. */
def findEditionForName(name: String): Try[Editions.Raw.Edition]
}

View File

@ -0,0 +1,63 @@
package org.enso.editions.provider
import org.enso.editions.{EditionSerialization, Editions}
import java.io.FileNotFoundException
import java.nio.file.{Files, Path}
import scala.annotation.tailrec
import scala.util.{Failure, Success, Try}
/** An implementation of [[EditionProvider]] that looks for the edition files in
* a list of filesystem paths.
*/
case class FileSystemEditionProvider(searchPaths: List[Path])
extends EditionProvider {
/** @inheritdoc */
override def findEditionForName(name: String): Try[Editions.Raw.Edition] = {
val result = findEdition(name, searchPaths)
result match {
case Left(EditionNotFound) =>
Failure(new FileNotFoundException(s"Could not find edition `$name`."))
case Left(EditionReadError(error)) => Failure(error)
case Right(value) => Success(value)
}
}
private val editionSuffix = ".yaml"
@tailrec
private def findEdition(
name: String,
paths: List[Path]
): Either[EditionLoadingError, Editions.Raw.Edition] = paths match {
case head :: tail =>
val headResult = loadEdition(name, head)
headResult match {
case Left(EditionNotFound) =>
findEdition(name, tail)
case _ => headResult
}
case Nil => Left(EditionNotFound)
}
sealed private trait EditionLoadingError
private case object EditionNotFound extends EditionLoadingError
private case class EditionReadError(error: Throwable)
extends EditionLoadingError
private def loadEdition(
name: String,
path: Path
): Either[EditionLoadingError, Editions.Raw.Edition] = {
val fileName = name + editionSuffix
val editionPath = path.resolve(fileName)
if (Files.exists(editionPath)) {
EditionSerialization
.loadEdition(editionPath)
.toEither
.left
.map(EditionReadError)
} else Left(EditionNotFound)
}
}

View File

@ -0,0 +1,157 @@
package org.enso.editions
import nl.gn0s1s.bump.SemVer
import org.enso.editions.EditionResolutionError.EditionResolutionCycle
import org.enso.editions.Editions.Repository
import org.enso.editions.provider.EditionProvider
import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AnyWordSpec
import org.scalatest.{Inside, OptionValues}
import scala.util.{Failure, Success, Try}
class EditionResolverSpec
extends AnyWordSpec
with Matchers
with Inside
with OptionValues {
object FakeEditionProvider extends EditionProvider {
val mainRepo = Repository.make("main", "https://example.com/main").get
val editions: Map[String, Editions.RawEdition] = Map(
"2021.0" -> Editions.Raw.Edition(
parent = None,
engineVersion = Some(SemVerEnsoVersion(SemVer(1, 2, 3))),
repositories = Map(
"main" -> mainRepo
),
libraries = Map(
"Standard.Base" -> Editions.Raw
.PublishedLibrary("Standard.Base", SemVer(1, 2, 3), "main")
)
),
"cycleA" -> Editions.Raw.Edition(
parent = Some("cycleB"),
engineVersion = Some(SemVerEnsoVersion(SemVer(1, 2, 3))),
repositories = Map(),
libraries = Map()
),
"cycleB" -> Editions.Raw.Edition(
parent = Some("cycleA"),
engineVersion = Some(SemVerEnsoVersion(SemVer(1, 2, 3))),
repositories = Map(),
libraries = Map()
)
)
override def findEditionForName(name: String): Try[Editions.Raw.Edition] =
editions
.get(name)
.map(Success(_))
.getOrElse(
Failure(new RuntimeException(s"Could not load edition `$name`."))
)
}
val resolver = EditionResolver(FakeEditionProvider)
"EditionResolver" should {
"resolve a simple edition" in {
val repo = Repository.make("foo", "http://example.com").get
val edition = Editions.Raw.Edition(
parent = None,
engineVersion = Some(SemVerEnsoVersion(SemVer(1, 2, 3))),
repositories = Map("foo" -> repo),
libraries = Map(
"bar.baz" -> Editions.Raw.LocalLibrary("bar.baz"),
"foo.bar" -> Editions.Raw
.PublishedLibrary("foo.bar", SemVer(1, 2, 3), "foo")
)
)
inside(resolver.resolve(edition)) { case Right(resolved) =>
resolved.engineVersion shouldEqual edition.engineVersion
resolved.parent should be(empty)
resolved.repositories shouldEqual edition.repositories
resolved.libraries should have size 2
resolved.libraries("bar.baz") shouldEqual Editions.Resolved
.LocalLibrary("bar.baz")
resolved.libraries("foo.bar") shouldEqual Editions.Resolved
.PublishedLibrary("foo.bar", SemVer(1, 2, 3), repo)
}
}
"resolve a nested edition" in {
val edition = Editions.Raw.Edition(
parent = Some("2021.0"),
engineVersion = Some(SemVerEnsoVersion(SemVer(1, 2, 3))),
repositories = Map(),
libraries = Map(
"bar.baz" -> Editions.Raw.LocalLibrary("bar.baz"),
"foo.bar" -> Editions.Raw
.PublishedLibrary("foo.bar", SemVer(1, 2, 3), "main")
)
)
inside(resolver.resolve(edition)) { case Right(resolved) =>
resolved.parent should be(defined)
resolved.libraries should have size 2
resolved.libraries("bar.baz") shouldEqual Editions.Resolved
.LocalLibrary("bar.baz")
resolved.libraries("foo.bar") shouldEqual Editions.Resolved
.PublishedLibrary(
"foo.bar",
SemVer(1, 2, 3),
FakeEditionProvider.mainRepo
)
}
}
"correctly handle repository shadowing when resolving libraries" in {
val localRepo = Repository.make("main", "http://example.com/local").get
localRepo should not equal FakeEditionProvider.mainRepo
val edition = Editions.Raw.Edition(
parent = Some("2021.0"),
engineVersion = None,
repositories = Map("main" -> localRepo),
libraries = Map(
"foo.bar" -> Editions.Raw
.PublishedLibrary("foo.bar", SemVer(1, 2, 3), "main")
)
)
inside(resolver.resolve(edition)) { case Right(resolved) =>
resolved.parent should be(defined)
resolved.libraries should have size 1
resolved.libraries("foo.bar") shouldEqual
Editions.Resolved.PublishedLibrary(
"foo.bar",
SemVer(1, 2, 3),
localRepo
)
resolved.parent.value.libraries("Standard.Base") shouldEqual
Editions.Resolved.PublishedLibrary(
"Standard.Base",
SemVer(1, 2, 3),
FakeEditionProvider.mainRepo
)
}
}
"avoid cycles in the resolution" in {
val edition = Editions.Raw.Edition(
parent = Some("cycleA"),
engineVersion = Some(SemVerEnsoVersion(SemVer(1, 2, 3))),
repositories = Map(),
libraries = Map()
)
val failure = resolver.resolve(edition)
inside(failure) { case Left(EditionResolutionCycle(editions)) =>
editions shouldEqual Seq("cycleA", "cycleB", "cycleA")
}
}
}
}

View File

@ -0,0 +1,121 @@
package org.enso.editions
import nl.gn0s1s.bump.SemVer
import org.scalatest.Inside
import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AnyWordSpec
import scala.util.{Failure, Success}
class EditionSerializationSpec extends AnyWordSpec with Matchers with Inside {
"EditionSerialization" should {
"parse an edition that just derives another one" in {
val parsed = EditionSerialization.parseYamlString(
"""extends: 2021.4
|""".stripMargin
)
inside(parsed) { case Success(edition) =>
edition.parent should contain("2021.4")
edition.repositories should be(empty)
edition.libraries should be(empty)
edition.engineVersion should be(empty)
}
}
"parse a standalone edition" in {
val parsed = EditionSerialization.parseYamlString(
"""engine-version: 1.2.3-SNAPSHOT
|repositories:
| - name: example
| url: https://example.com/
| - name: foo
| url: http://127.0.0.1:8080/root
|libraries:
| - name: Foo.Local
| repository: local
| - name: Bar.Baz
| repository: example
| version: 0.0.0
| - name: A.B
| repository: bar
| version: 1.0.1
|""".stripMargin
)
inside(parsed) { case Success(edition) =>
edition.parent should be(empty)
edition.repositories.size shouldEqual 2
edition.repositories("example").name shouldEqual "example"
edition
.repositories("example")
.url
.toString shouldEqual "https://example.com/"
edition.repositories("foo").name shouldEqual "foo"
edition
.repositories("foo")
.url
.toString shouldEqual "http://127.0.0.1:8080/root"
edition.libraries.values should contain theSameElementsAs Seq(
Editions.Raw.LocalLibrary("Foo.Local"),
Editions.Raw.PublishedLibrary("Bar.Baz", SemVer(0, 0, 0), "example"),
Editions.Raw.PublishedLibrary("A.B", SemVer(1, 0, 1), "bar")
)
edition.engineVersion should contain(
SemVerEnsoVersion(SemVer(1, 2, 3, Some("SNAPSHOT")))
)
}
}
"not parse an edition that does not define the engine version nor attempt to derive it" in {
val parsed = EditionSerialization.parseYamlString(
"""libraries:
|- name: foo
| repository: local
|""".stripMargin
)
inside(parsed) { case Failure(exception) =>
exception.getMessage should include(
"The edition must specify at least one of"
)
}
}
"not allow invalid urls for repositories" in {
val parsed = EditionSerialization.parseYamlString(
"""extends: foo
|repositories:
|- name: bar
| url: baz
|""".stripMargin
)
inside(parsed) { case Failure(exception) =>
exception.getMessage should include("Cannot parse an URL")
}
}
"not allow invalid version combinations for libraries" in {
val parsed = EditionSerialization.parseYamlString(
"""extends: foo
|libraries:
|- name: bar
| repository: local
| version: 1.2.3-SHOULD-NOT-BE-HERE
|""".stripMargin
)
inside(parsed) { case Failure(exception) =>
exception.getMessage should include("Version field must not be set")
}
val parsed2 = EditionSerialization.parseYamlString(
"""extends: foo
|libraries:
|- name: bar
| repository: something
|""".stripMargin
)
inside(parsed2) { case Failure(exception) =>
exception.getMessage should include("Version field is mandatory")
}
}
}
}

View File

@ -0,0 +1,61 @@
package org.enso.librarymanager
import org.enso.distribution.DistributionManager
import org.enso.editions.{Editions, LibraryName}
import org.enso.librarymanager.local.LocalLibraryProvider
import org.enso.librarymanager.published.{
DefaultPublishedLibraryProvider,
PublishedLibraryProvider
}
/** A helper class for loading libraries. */
case class LibraryLoader(distributionManager: DistributionManager) {
private val localLibraryProvider =
LocalLibraryProvider.make(distributionManager)
private val resolver = LibraryResolver(localLibraryProvider)
private val publishedLibraryProvider: PublishedLibraryProvider =
new DefaultPublishedLibraryProvider(distributionManager)
/** Resolves the library version that should be used based on the
* configuration and returns its location on the filesystem.
*
* If the library is not available, this operation may download it.
*/
def findLibrary(
libraryName: LibraryName,
edition: Editions.ResolvedEdition,
preferLocalLibraries: Boolean
): LibraryResolutionResult = {
val resolvedVersion = resolver
.resolveLibraryVersion(libraryName, edition, preferLocalLibraries)
resolvedVersion match {
case Left(error) =>
LibraryResolutionResult.ResolutionFailure(error)
case Right(LibraryVersion.Local) =>
localLibraryProvider
.findLibrary(libraryName)
.map(LibraryResolutionResult.ResolvedImmediately)
.getOrElse {
LibraryResolutionResult.ResolutionFailure(
LibraryResolutionError(
s"Edition configuration forces to use the local version, but " +
s"the `$libraryName` library is not present among local " +
s"libraries."
)
)
}
case Right(LibraryVersion.Published(version, repository)) =>
val dependencyResolver = { name: LibraryName =>
resolver
.resolveLibraryVersion(name, edition, preferLocalLibraries)
.toOption
}
publishedLibraryProvider.findLibrary(
libraryName,
version,
repository,
dependencyResolver
)
}
}
}

View File

@ -0,0 +1,5 @@
package org.enso.librarymanager
/** Indicates an error of library resolution. */
case class LibraryResolutionError(message: String)
extends RuntimeException(message)

View File

@ -0,0 +1,32 @@
package org.enso.librarymanager
import org.enso.cli.task.TaskProgress
import java.nio.file.Path
/** Encapsulates possible results of library resolution. */
sealed trait LibraryResolutionResult
object LibraryResolutionResult {
/** Indicates that the resolution has failed immediately with an error. */
case class ResolutionFailure(error: Throwable) extends LibraryResolutionResult
/** Indicates that the resolution has succeeded immediately.
*
* The library was already available on disk and the path to it is returned.
*/
case class ResolvedImmediately(path: Path) extends LibraryResolutionResult
/** Indicates that the initial library resolution has succeeded (that the
* corrrect version could be inferred from the edition) but the library was
* not installed, so it must be downloaded.
*
* It contains a [[TaskProgress]] instance that can track the progress of the
* download and will be completed once the library has been installed.
*
* The download and installation may of course fail as well, which is
* indicated by failure of the [[TaskProgress]].
*/
case class ResolutionPending(result: TaskProgress[Path])
extends LibraryResolutionResult
}

View File

@ -0,0 +1,78 @@
package org.enso.librarymanager
import org.enso.editions.{Editions, LibraryName}
import org.enso.librarymanager.local.LocalLibraryProvider
/** A helper class that figures out which library version should be used in a
* given configuration.
*
* It needs a [[LocalLibraryProvider]] instance, because if
* `preferLocalLibraries` is set to true, the resulting library version depends
* on whether a local library with a given name exists.
*/
case class LibraryResolver(
localLibraryProvider: LocalLibraryProvider
) {
/** Resolves the library version that entails from the provided configuration.
*
* The configuration consists of the edition and the flag specifying if local
* libraries should override the edition settings.
*
* @param libraryName the name of the library to resolve
* @param edition the edition configuration
* @param preferLocalLibraries the flag indicating whether to prefer local
* libraries that are present over what the
* edition defines
* @return
*/
def resolveLibraryVersion(
libraryName: LibraryName,
edition: Editions.ResolvedEdition,
preferLocalLibraries: Boolean
): Either[LibraryResolutionError, LibraryVersion] = {
if (preferLocalLibraries) {
localLibraryProvider
.findLibrary(libraryName)
.map(_ => Right(LibraryVersion.Local))
.getOrElse {
resolveLibraryFromEdition(libraryName, edition)
}
} else resolveLibraryFromEdition(libraryName, edition)
}
/** Resolves the library version that entails from the edition.
*
* @param libraryName the name of the library to resolve
* @param edition the edition configuration
* @return either an error (if the library was not found in the edition) or
* the entailed version
*/
def resolveLibraryFromEdition(
libraryName: LibraryName,
edition: Editions.ResolvedEdition
): Either[LibraryResolutionError, LibraryVersion] = {
import Editions.Resolved._
val immediateResult =
edition.libraries.get(libraryName.qualifiedName).map {
case LocalLibrary(_) =>
Right(LibraryVersion.Local)
case PublishedLibrary(_, version, repository) =>
Right(LibraryVersion.Published(version, repository))
}
immediateResult.getOrElse {
edition.parent match {
case Some(parentEdition) =>
resolveLibraryFromEdition(libraryName, parentEdition)
case None =>
Left(
LibraryResolutionError(
s"The library `$libraryName` is not defined within " +
s"the edition."
)
)
}
}
}
}

View File

@ -0,0 +1,20 @@
package org.enso.librarymanager
import nl.gn0s1s.bump.SemVer
import org.enso.editions.Editions.Repository
/** A resolved version of the library. */
sealed trait LibraryVersion
object LibraryVersion {
/** Indicates that the version from the local library path should be used. */
case object Local extends LibraryVersion
/** Indicates that a particular published version should be used.
*
* It also indicates the repository to download the library from if it is not
* already cached.
*/
case class Published(version: SemVer, repository: Repository)
extends LibraryVersion
}

View File

@ -0,0 +1,37 @@
package org.enso.librarymanager.local
import org.enso.distribution.DistributionManager
import org.enso.editions.LibraryName
import org.enso.distribution.FileSystem.PathSyntax
import java.nio.file.{Files, Path}
import scala.annotation.tailrec
/** A default implementation of [[LocalLibraryProvider]]. */
class DefaultLocalLibraryProvider(distributionManager: DistributionManager)
extends LocalLibraryProvider {
/** @inheritdoc */
override def findLibrary(libraryName: LibraryName): Option[Path] =
findLibraryHelper(
libraryName,
distributionManager.paths.localLibrariesSearchPaths.toList
)
/** Searches through the available library paths, checking if any one of them contains the requested library.
*
* The first path on the list takes precedence.
*/
@tailrec
private def findLibraryHelper(
libraryName: LibraryName,
searchPaths: List[Path]
): Option[Path] = searchPaths match {
case head :: tail =>
val potentialPath = head / libraryName.prefix / libraryName.name
if (Files.exists(potentialPath) && Files.isDirectory(potentialPath))
Some(potentialPath)
else findLibraryHelper(libraryName, tail)
case Nil => None
}
}

View File

@ -0,0 +1,23 @@
package org.enso.librarymanager.local
import org.enso.distribution.DistributionManager
import org.enso.editions.LibraryName
import java.nio.file.Path
/** A provider for local libraries. */
trait LocalLibraryProvider {
/** Returns the path to a local instance of the requested library, if it is
* available.
*/
def findLibrary(libraryName: LibraryName): Option[Path]
}
object LocalLibraryProvider {
/** Creates a default [[LocalLibraryProvider]] from a [[DistributionManager]].
*/
def make(distributionManager: DistributionManager): LocalLibraryProvider =
new DefaultLocalLibraryProvider(distributionManager)
}

View File

@ -0,0 +1,28 @@
package org.enso.librarymanager.published
import nl.gn0s1s.bump.SemVer
import org.enso.distribution.DistributionManager
import org.enso.editions.{Editions, LibraryName}
import org.enso.librarymanager.{LibraryResolutionResult, LibraryVersion}
import scala.annotation.nowarn
/** A default implementation of [[PublishedLibraryProvider]]. */
class DefaultPublishedLibraryProvider(
@nowarn("msg=never used") distributionManager: DistributionManager
) extends PublishedLibraryProvider {
// TODO [RW] This is just a stub and will be properly implemented in #1772.
/** @inheritdoc */
override def findLibrary(
libraryName: LibraryName,
version: SemVer,
recommendedRepository: Editions.Repository,
dependencyResolver: LibraryName => Option[LibraryVersion]
): LibraryResolutionResult = LibraryResolutionResult.ResolutionFailure(
new NotImplementedError(
"TODO library management is not yet implemented"
)
)
}

View File

@ -0,0 +1,27 @@
package org.enso.librarymanager.published
import nl.gn0s1s.bump.SemVer
import org.enso.editions.Editions.Repository
import org.enso.editions.LibraryName
import org.enso.librarymanager.{LibraryResolutionResult, LibraryVersion}
/** A provider of published libraries.
*
* It usually should use some kind of a cache to keep already downloaded
* libraries and download new libraries on demand.
*/
trait PublishedLibraryProvider {
/** Tries to locate the requested library at a specific version.
*
* If the library is not present, a download may be attempted - this is where
* the `recommendedRepository` is used to guide which repository should be
* used for the download.
*/
def findLibrary(
libraryName: LibraryName,
version: SemVer,
recommendedRepository: Repository,
dependencyResolver: LibraryName => Option[LibraryVersion]
): LibraryResolutionResult
}

View File

@ -0,0 +1,146 @@
package org.enso.librarymanager
import nl.gn0s1s.bump.SemVer
import org.enso.editions.Editions.Repository
import org.enso.editions.{DefaultEnsoVersion, Editions, LibraryName}
import org.enso.librarymanager.local.LocalLibraryProvider
import org.enso.testkit.EitherValue
import org.scalatest.Inside
import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AnyWordSpec
import java.nio.file.Path
class LibraryResolverSpec
extends AnyWordSpec
with Matchers
with EitherValue
with Inside {
"LibraryResolver" should {
val mainRepo = Repository.make("main", "https://example.com/main").get
val parentEdition = Editions.Resolved.Edition(
parent = None,
engineVersion = Some(DefaultEnsoVersion),
repositories = Map("main" -> mainRepo),
libraries = Map(
"Standard.Base" -> Editions.Resolved
.PublishedLibrary("Standard.Base", SemVer(4, 5, 6), mainRepo)
)
)
val customRepo = Repository.make("custom", "https://example.com/custom").get
val currentEdition = Editions.Resolved.Edition(
parent = Some(parentEdition),
engineVersion = None,
repositories = Map("custom" -> customRepo),
libraries = Map(
"Foo.Main" -> Editions.Resolved
.PublishedLibrary("Foo.Main", SemVer(1, 0, 0), mainRepo),
"Foo.My" -> Editions.Resolved
.PublishedLibrary("Foo.My", SemVer(2, 0, 0), customRepo),
"Foo.Local" -> Editions.Resolved.LocalLibrary("Foo.Local")
)
)
case class FakeLocalLibraryProvider(fixtures: Map[String, Path])
extends LocalLibraryProvider {
override def findLibrary(libraryName: LibraryName): Option[Path] =
fixtures.get(libraryName.qualifiedName)
}
val localLibraries = Map(
"Foo.My" -> Path.of("./Foo/My"),
"Foo.Local" -> Path.of("./Foo/Local"),
"Standard.Base" -> Path.of("./Standard/Base")
)
val resolver = LibraryResolver(FakeLocalLibraryProvider(localLibraries))
"correctly resolve libraries based on the edition" in {
resolver
.resolveLibraryVersion(
libraryName = LibraryName("Standard", "Base"),
edition = currentEdition,
preferLocalLibraries = false
)
.rightValue shouldEqual
LibraryVersion.Published(SemVer(4, 5, 6), mainRepo)
resolver
.resolveLibraryVersion(
libraryName = LibraryName("Foo", "Main"),
edition = currentEdition,
preferLocalLibraries = false
)
.rightValue shouldEqual
LibraryVersion.Published(SemVer(1, 0, 0), mainRepo)
resolver
.resolveLibraryVersion(
libraryName = LibraryName("Foo", "My"),
edition = currentEdition,
preferLocalLibraries = false
)
.rightValue shouldEqual
LibraryVersion.Published(SemVer(2, 0, 0), customRepo)
resolver
.resolveLibraryVersion(
libraryName = LibraryName("Foo", "Local"),
edition = currentEdition,
preferLocalLibraries = false
)
.rightValue shouldEqual
LibraryVersion.Local
}
"correctly handle preference of local libraries" in {
resolver
.resolveLibraryVersion(
libraryName = LibraryName("Standard", "Base"),
edition = currentEdition,
preferLocalLibraries = true
)
.rightValue shouldEqual
LibraryVersion.Local
resolver
.resolveLibraryVersion(
libraryName = LibraryName("Foo", "Main"),
edition = currentEdition,
preferLocalLibraries = true
)
.rightValue shouldEqual
LibraryVersion.Published(SemVer(1, 0, 0), mainRepo)
resolver
.resolveLibraryVersion(
libraryName = LibraryName("Foo", "My"),
edition = currentEdition,
preferLocalLibraries = true
)
.rightValue shouldEqual
LibraryVersion.Local
resolver
.resolveLibraryVersion(
libraryName = LibraryName("Foo", "Local"),
edition = currentEdition,
preferLocalLibraries = true
)
.rightValue shouldEqual
LibraryVersion.Local
}
"not fall back to a local library if it was not defined as such " +
"explicitly nor `prefer-local-libraries` is set" in {
val result = resolver.resolveLibraryVersion(
libraryName = LibraryName("Foo", "Local"),
edition = parentEdition,
preferLocalLibraries = false
)
inside(result) { case Left(error) =>
error.getMessage should include("not defined")
}
}
}
}

View File

@ -1,18 +1,13 @@
package org.enso.pkg package org.enso.pkg
import io.circe.syntax._ import io.circe.syntax._
import io.circe.generic.auto._ import io.circe.{yaml, Decoder, DecodingFailure, Encoder, Json, JsonObject}
import io.circe.{yaml, Decoder, DecodingFailure, Encoder, Json}
import io.circe.yaml.Printer import io.circe.yaml.Printer
import org.enso.editions.{DefaultEnsoVersion, Editions, EnsoVersion}
import org.enso.editions.EditionSerialization._
import scala.util.Try import scala.util.Try
/** An extra project dependency.
* @param name name of the package
* @param version package version
*/
case class Dependency(name: String, version: String)
/** Contact information to a user. /** Contact information to a user.
* *
* Used for defining authors and maintainers. * Used for defining authors and maintainers.
@ -26,8 +21,7 @@ case class Contact(name: Option[String], email: Option[String]) {
"At least one of fields `name` or `email` must be defined." "At least one of fields `name` or `email` must be defined."
) )
/** @inheritdoc /** @inheritdoc */
*/
override def toString: String = { override def toString: String = {
val space = if (name.isDefined && email.isDefined) " " else "" val space = if (name.isDefined && email.isDefined) " " else ""
name.getOrElse("") + space + email.map(email => s"<$email>").getOrElse("") name.getOrElse("") + space + email.map(email => s"<$email>").getOrElse("")
@ -35,15 +29,13 @@ case class Contact(name: Option[String], email: Option[String]) {
} }
object Contact { object Contact {
/** Fields for use when serializing the [[Contact]]. /** Fields for use when serializing the [[Contact]]. */
*/
object Fields { object Fields {
val Name = "name" val Name = "name"
val Email = "email" val Email = "email"
} }
/** [[Encoder]] instance for the [[Contact]]. /** [[Encoder]] instance for the [[Contact]]. */
*/
implicit val encoder: Encoder[Contact] = { contact => implicit val encoder: Encoder[Contact] = { contact =>
val name = contact.name.map(Fields.Name -> _.asJson) val name = contact.name.map(Fields.Name -> _.asJson)
val email = contact.email.map(Fields.Email -> _.asJson) val email = contact.email.map(Fields.Email -> _.asJson)
@ -78,14 +70,15 @@ object Contact {
* *
* @param name package name * @param name package name
* @param version package version * @param version package version
* @param ensoVersion version of the Enso engine associated with the package,
* can be set to `default` which defaults to the locally
* installed version
* @param license package license * @param license package license
* @param authors name and contact information of the package author(s) * @param authors name and contact information of the package author(s)
* @param maintainers name and contact information of current package * @param maintainers name and contact information of current package
* maintainer(s) * maintainer(s)
* @param dependencies a list of package dependencies * @param edition the Edition associated with the project; it implies the
* engine version and dependency configuration to be used
* @param preferLocalLibraries specifies if library resolution should prefer
* local libraries over what is defined in the
* edition
* @param originalJson a Json object holding the original values that this * @param originalJson a Json object holding the original values that this
* Config was created from, used to preserve configuration * Config was created from, used to preserve configuration
* keys that are not known * keys that are not known
@ -93,16 +86,15 @@ object Contact {
case class Config( case class Config(
name: String, name: String,
version: String, version: String,
ensoVersion: EnsoVersion,
license: String, license: String,
authors: List[Contact], authors: List[Contact],
maintainers: List[Contact], maintainers: List[Contact],
dependencies: List[Dependency], edition: Editions.RawEdition,
originalJson: Json = Json.obj() preferLocalLibraries: Boolean,
originalJson: JsonObject = JsonObject()
) { ) {
/** Converts the configuration into a YAML representation. /** Converts the configuration into a YAML representation. */
*/
def toYaml: String = def toYaml: String =
Printer.spaces2.copy(preserveOrder = true).pretty(Config.encoder(this)) Printer.spaces2.copy(preserveOrder = true).pretty(Config.encoder(this))
} }
@ -115,56 +107,107 @@ object Config {
val license: String = "license" val license: String = "license"
val author: String = "authors" val author: String = "authors"
val maintainer: String = "maintainers" val maintainer: String = "maintainers"
val dependencies: String = "dependencies" val edition: String = "edition"
val preferLocalLibraries = "prefer-local-libraries"
} }
implicit val decoder: Decoder[Config] = { json => implicit val decoder: Decoder[Config] = { json =>
for { for {
name <- json.get[String](JsonFields.name) name <- json.get[String](JsonFields.name)
version <- json.getOrElse[String](JsonFields.version)("dev") version <- json.getOrElse[String](JsonFields.version)("dev")
ensoVersion <- ensoVersion <- json.get[Option[EnsoVersion]](JsonFields.ensoVersion)
json.getOrElse[EnsoVersion](JsonFields.ensoVersion)(DefaultEnsoVersion) edition <- json.get[Option[Editions.RawEdition]](JsonFields.edition)
license <- json.getOrElse(JsonFields.license)("") license <- json.getOrElse(JsonFields.license)("")
author <- json.getOrElse[List[Contact]](JsonFields.author)(List()) author <- json.getOrElse[List[Contact]](JsonFields.author)(List())
maintainer <- json.getOrElse[List[Contact]](JsonFields.maintainer)(List()) maintainer <- json.getOrElse[List[Contact]](JsonFields.maintainer)(List())
dependencies <- json.getOrElse[List[Dependency]](JsonFields.dependencies)( preferLocal <-
List() json.getOrElse[Boolean](JsonFields.preferLocalLibraries)(false)
) finalEdition <-
editionOrVersionBackwardsCompatibility(edition, ensoVersion).left.map {
error => DecodingFailure(error, json.history)
}
originals <- json.as[JsonObject]
} yield Config( } yield Config(
name, name = name,
version, version = version,
ensoVersion, license = license,
license, authors = author,
author, maintainers = maintainer,
maintainer, edition = finalEdition,
dependencies, preferLocalLibraries = preferLocal,
json.value originalJson = originals
) )
} }
implicit val encoder: Encoder[Config] = { config => implicit val encoder: Encoder[Config] = { config =>
val originals = config.originalJson val originals = config.originalJson
val overrides = Json.obj( val overrides = Seq(
JsonFields.name -> config.name.asJson, JsonFields.name -> config.name.asJson,
JsonFields.version -> config.version.asJson, JsonFields.version -> config.version.asJson,
JsonFields.ensoVersion -> config.ensoVersion.asJson, JsonFields.edition -> config.edition.asJson,
JsonFields.license -> config.license.asJson, JsonFields.license -> config.license.asJson,
JsonFields.author -> config.authors.asJson, JsonFields.author -> config.authors.asJson,
JsonFields.maintainer -> config.maintainers.asJson JsonFields.maintainer -> config.maintainers.asJson
) )
val base = originals.deepMerge(overrides) val preferLocalOverride =
val withDeps = if (config.preferLocalLibraries)
if (config.dependencies.nonEmpty) Seq(JsonFields.preferLocalLibraries -> true.asJson)
base.deepMerge( else Seq()
Json.obj(JsonFields.dependencies -> config.dependencies.asJson) val overridesObject = JsonObject(
) overrides ++ preferLocalOverride: _*
else base )
withDeps originals.remove(JsonFields.ensoVersion).deepMerge(overridesObject).asJson
} }
/** Tries to parse the [[Config]] from a YAML string. /** Tries to parse the [[Config]] from a YAML string. */
*/
def fromYaml(yamlString: String): Try[Config] = { def fromYaml(yamlString: String): Try[Config] = {
yaml.parser.parse(yamlString).flatMap(_.as[Config]).toTry yaml.parser.parse(yamlString).flatMap(_.as[Config]).toTry
} }
/** Creates a simple edition that just defines the provided engine version.
*
* A compatibility layer for migrating from just specifying the engine
* version to the edition system.
*
* TODO [RW] once the edition is actually used for loading libraries, this
* may need to be revisited, because an edition created in this way will not
* have any libraries present which is highly undesirable. We may either
* remove the compatibility layer and return errors for the old format or
* need to use the latest/default edition or some hardcoded edition for
* compatibility.
*/
def makeCompatibilityEditionFromVersion(
ensoVersion: EnsoVersion
): Editions.RawEdition = Editions.Raw.Edition(
parent = None,
engineVersion = Some(ensoVersion),
repositories = Map(),
libraries = Map()
)
/** A helper method that reconciles the old and new fields of the config
* related to the edition.
*
* If an edition is present, it is just returned as-is. If the engine version
* is specified, a special edition is created that specifies this particular
* engine version and nothing else.
*
* If both fields are defined, an error is raised as the configuration may be
* inconsistent - the `engine-version` field should only be present in old
* configs and after migration to the edition format it should be removed.
*/
private def editionOrVersionBackwardsCompatibility(
edition: Option[Editions.RawEdition],
ensoVersion: Option[EnsoVersion]
): Either[String, Editions.RawEdition] = (edition, ensoVersion) match {
case (Some(_), Some(_)) =>
Left(
s"The deprecated `${JsonFields.ensoVersion}` should not be defined " +
s"if the `${JsonFields.edition}` that replaces it is already defined."
)
case (Some(edition), _) => Right(edition)
case _ =>
val version = ensoVersion.getOrElse(DefaultEnsoVersion)
Right(makeCompatibilityEditionFromVersion(version))
}
} }

View File

@ -1,14 +1,13 @@
package org.enso.pkg package org.enso.pkg
import java.io.File
import cats.Show import cats.Show
import org.enso.editions.{DefaultEnsoVersion, EnsoVersion}
import scala.jdk.CollectionConverters._
import org.enso.filesystem.FileSystem import org.enso.filesystem.FileSystem
import org.enso.pkg.validation.NameValidation import org.enso.pkg.validation.NameValidation
import java.io.File
import scala.io.Source import scala.io.Source
import scala.jdk.CollectionConverters._
import scala.util.{Failure, Try, Using} import scala.util.{Failure, Try, Using}
object CouldNotCreateDirectory extends Exception object CouldNotCreateDirectory extends Exception
@ -202,14 +201,15 @@ class PackageManager[F](implicit val fileSystem: FileSystem[F]) {
authors: List[Contact] = List(), authors: List[Contact] = List(),
maintainers: List[Contact] = List() maintainers: List[Contact] = List()
): Package[F] = { ): Package[F] = {
val edition = Config.makeCompatibilityEditionFromVersion(ensoVersion)
val config = Config( val config = Config(
name = NameValidation.normalizeName(name), name = NameValidation.normalizeName(name),
version = version, version = version,
ensoVersion = ensoVersion, license = "",
license = "", authors = authors,
authors = authors, edition = edition,
maintainers = maintainers, preferLocalLibraries = true,
dependencies = List() maintainers = maintainers
) )
create(root, config) create(root, config)
} }

View File

@ -1,6 +1,8 @@
package org.enso.pkg package org.enso.pkg
import io.circe.Json import io.circe.{Json, JsonObject}
import nl.gn0s1s.bump.SemVer
import org.enso.editions.{DefaultEnsoVersion, SemVerEnsoVersion}
import org.scalatest.{Inside, OptionValues} import org.scalatest.{Inside, OptionValues}
import org.scalatest.matchers.should.Matchers import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AnyWordSpec import org.scalatest.wordspec.AnyWordSpec
@ -29,21 +31,52 @@ class ConfigSpec
"deserialize the serialized representation to the original value" in { "deserialize the serialized representation to the original value" in {
val config = Config( val config = Config(
name = "placeholder", name = "placeholder",
version = "dev", version = "dev",
ensoVersion = DefaultEnsoVersion, edition = Config.makeCompatibilityEditionFromVersion(
license = "none", SemVerEnsoVersion(SemVer(4, 5, 6))
authors = List(), ),
license = "none",
authors = List(),
maintainers = List( maintainers = List(
Contact(Some("A"), Some("a@example.com")), Contact(Some("A"), Some("a@example.com")),
Contact(Some("B"), None), Contact(Some("B"), None),
Contact(None, Some("c@example.com")) Contact(None, Some("c@example.com"))
), ),
dependencies = List(Dependency("dependency", "0.0.0")) preferLocalLibraries = true
) )
val deserialized = Config.fromYaml(config.toYaml).get val deserialized = Config.fromYaml(config.toYaml).get
val withoutJson = deserialized.copy(originalJson = Json.obj()) val withoutJson = deserialized.copy(originalJson = JsonObject())
withoutJson shouldEqual config withoutJson shouldEqual config
} }
"only require the name and use defaults for everything else" in {
val parsed = Config.fromYaml("name: FooBar").get
parsed.name shouldEqual "FooBar"
parsed.edition.engineVersion should contain(DefaultEnsoVersion)
}
"be backwards compatible but correctly migrate to new format on save" in {
val oldFormat =
"""name: FooBar
|enso-version: 1.2.3
|extra-key: extra-value
|""".stripMargin
val parsed = Config.fromYaml(oldFormat).get
parsed.edition.engineVersion should contain(
SemVerEnsoVersion(SemVer(1, 2, 3))
)
val serialized = parsed.toYaml
val parsedAgain = Config.fromYaml(serialized).get
parsedAgain.copy(originalJson = JsonObject()) shouldEqual
parsed.copy(originalJson = JsonObject())
parsedAgain.originalJson("extra-key").flatMap(_.asString) should contain(
"extra-value"
)
}
} }
} }

View File

@ -3,13 +3,13 @@ package org.enso.projectmanager.infrastructure.languageserver
import java.io.PrintWriter import java.io.PrintWriter
import java.lang.ProcessBuilder.Redirect import java.lang.ProcessBuilder.Redirect
import java.util.concurrent.Executors import java.util.concurrent.Executors
import akka.actor.ActorRef import akka.actor.ActorRef
import com.typesafe.scalalogging.Logger import com.typesafe.scalalogging.Logger
import org.apache.commons.lang3.concurrent.BasicThreadFactory import org.apache.commons.lang3.concurrent.BasicThreadFactory
import org.enso.logger.masking.Masking import org.enso.logger.masking.Masking
import org.enso.loggingservice.LoggingServiceManager import org.enso.loggingservice.LoggingServiceManager
import org.enso.projectmanager.service.versionmanagement.RuntimeVersionManagerFactory import org.enso.projectmanager.service.versionmanagement.RuntimeVersionManagerFactory
import org.enso.runtimeversionmanager.config.GlobalConfigurationManager
import org.enso.runtimeversionmanager.runner.{LanguageServerOptions, Runner} import org.enso.runtimeversionmanager.runner.{LanguageServerOptions, Runner}
import scala.util.Using import scala.util.Using
@ -80,11 +80,16 @@ object ExecutorWithUnlimitedPool extends LanguageServerExecutor {
rpcPort = rpcPort, rpcPort = rpcPort,
dataPort = dataPort dataPort = dataPort
) )
val configurationManager = new GlobalConfigurationManager(
val runner = new Runner(
versionManager, versionManager,
distributionConfiguration.environment, distributionConfiguration.distributionManager
descriptor.deferredLoggingServiceEndpoint )
val runner = new Runner(
runtimeVersionManager = versionManager,
globalConfigurationManager = configurationManager,
editionManager = distributionConfiguration.editionManager,
environment = distributionConfiguration.environment,
loggerConnection = descriptor.deferredLoggingServiceEndpoint
) )
val runSettings = runner val runSettings = runner
.startLanguageServer( .startLanguageServer(

View File

@ -108,7 +108,7 @@ class ProjectFileRepository[
name = pkg.name, name = pkg.name,
kind = meta.kind, kind = meta.kind,
created = meta.created, created = meta.created,
engineVersion = pkg.config.ensoVersion, edition = pkg.config.edition,
lastOpened = meta.lastOpened, lastOpened = meta.lastOpened,
path = Some(directory.toString), path = Some(directory.toString),
directoryCreationTime = directoryCreationTime directoryCreationTime = directoryCreationTime

View File

@ -1,18 +1,18 @@
package org.enso.projectmanager.model package org.enso.projectmanager.model
import org.enso.editions.Editions
import java.nio.file.attribute.FileTime import java.nio.file.attribute.FileTime
import java.time.OffsetDateTime import java.time.OffsetDateTime
import java.util.UUID import java.util.UUID
import org.enso.pkg.{DefaultEnsoVersion, EnsoVersion}
/** Project entity. /** Project entity.
* *
* @param id a project id * @param id a project id
* @param name a project name * @param name a project name
* @param kind a project kind * @param kind a project kind
* @param created a project creation time * @param created a project creation time
* @param engineVersion version of the engine associated with the project * @param edition the edition configuration associated with the project
* @param lastOpened a project last open time * @param lastOpened a project last open time
* @param path a path to the project structure * @param path a path to the project structure
*/ */
@ -21,7 +21,7 @@ case class Project(
name: String, name: String,
kind: ProjectKind, kind: ProjectKind,
created: OffsetDateTime, created: OffsetDateTime,
engineVersion: EnsoVersion = DefaultEnsoVersion, edition: Editions.RawEdition,
lastOpened: Option[OffsetDateTime] = None, lastOpened: Option[OffsetDateTime] = None,
path: Option[String] = None, path: Option[String] = None,
directoryCreationTime: Option[FileTime] = None directoryCreationTime: Option[FileTime] = None

View File

@ -8,7 +8,7 @@ import org.enso.projectmanager.protocol.ProjectManagementApi._
* *
* Do not remove this import. * Do not remove this import.
*/ */
import org.enso.pkg.SemVerJson._ import org.enso.editions.SemVerJson._
object JsonRpc { object JsonRpc {

View File

@ -1,12 +1,11 @@
package org.enso.projectmanager.protocol package org.enso.projectmanager.protocol
import java.util.UUID import java.util.UUID
import io.circe.Json import io.circe.Json
import io.circe.syntax._ import io.circe.syntax._
import nl.gn0s1s.bump.SemVer import nl.gn0s1s.bump.SemVer
import org.enso.editions.EnsoVersion
import org.enso.jsonrpc.{Error, HasParams, HasResult, Method, Unused} import org.enso.jsonrpc.{Error, HasParams, HasResult, Method, Unused}
import org.enso.pkg.EnsoVersion
import org.enso.projectmanager.data.{ import org.enso.projectmanager.data.{
EngineVersion, EngineVersion,
MissingComponentAction, MissingComponentAction,

View File

@ -1,7 +1,7 @@
package org.enso.projectmanager.requesthandler package org.enso.projectmanager.requesthandler
import akka.actor._ import akka.actor._
import org.enso.pkg.DefaultEnsoVersion import org.enso.editions.DefaultEnsoVersion
import org.enso.projectmanager.control.core.CovariantFlatMap import org.enso.projectmanager.control.core.CovariantFlatMap
import org.enso.projectmanager.control.core.syntax._ import org.enso.projectmanager.control.core.syntax._
import org.enso.projectmanager.control.effect.syntax._ import org.enso.projectmanager.control.effect.syntax._

View File

@ -1,7 +1,5 @@
package org.enso.projectmanager.service package org.enso.projectmanager.service
import java.nio.file.Path
import akka.actor.ActorRef import akka.actor.ActorRef
import nl.gn0s1s.bump.SemVer import nl.gn0s1s.bump.SemVer
import org.enso.projectmanager.control.core.CovariantFlatMap import org.enso.projectmanager.control.core.CovariantFlatMap
@ -12,8 +10,11 @@ import org.enso.projectmanager.service.ProjectServiceFailure.ProjectCreateFailed
import org.enso.projectmanager.service.versionmanagement.RuntimeVersionManagerErrorRecoverySyntax._ import org.enso.projectmanager.service.versionmanagement.RuntimeVersionManagerErrorRecoverySyntax._
import org.enso.projectmanager.service.versionmanagement.RuntimeVersionManagerFactory import org.enso.projectmanager.service.versionmanagement.RuntimeVersionManagerFactory
import org.enso.projectmanager.versionmanagement.DistributionConfiguration import org.enso.projectmanager.versionmanagement.DistributionConfiguration
import org.enso.runtimeversionmanager.config.GlobalConfigurationManager
import org.enso.runtimeversionmanager.runner.Runner import org.enso.runtimeversionmanager.runner.Runner
import java.nio.file.Path
/** A service for creating new project structures using the runner of the /** A service for creating new project structures using the runner of the
* specific engine version selected for the project. * specific engine version selected for the project.
*/ */
@ -36,11 +37,17 @@ class ProjectCreationService[
val versionManager = RuntimeVersionManagerFactory( val versionManager = RuntimeVersionManagerFactory(
distributionConfiguration distributionConfiguration
).makeRuntimeVersionManager(progressTracker, missingComponentAction) ).makeRuntimeVersionManager(progressTracker, missingComponentAction)
val configurationManager = new GlobalConfigurationManager(
versionManager,
distributionConfiguration.distributionManager
)
val runner = val runner =
new Runner( new Runner(
versionManager, runtimeVersionManager = versionManager,
distributionConfiguration.environment, globalConfigurationManager = configurationManager,
loggingServiceDescriptor.getEndpoint editionManager = distributionConfiguration.editionManager,
environment = distributionConfiguration.environment,
loggerConnection = loggingServiceDescriptor.getEndpoint
) )
val settings = val settings =

View File

@ -1,16 +1,16 @@
package org.enso.projectmanager.service package org.enso.projectmanager.service
import java.util.UUID
import akka.actor.ActorRef import akka.actor.ActorRef
import cats.MonadError import cats.MonadError
import nl.gn0s1s.bump.SemVer import nl.gn0s1s.bump.SemVer
import org.enso.editions.{DefaultEnsoVersion, EnsoVersion}
import org.enso.pkg.Config
import org.enso.projectmanager.control.core.syntax._
import org.enso.projectmanager.control.core.{ import org.enso.projectmanager.control.core.{
Applicative, Applicative,
CovariantFlatMap, CovariantFlatMap,
Traverse Traverse
} }
import org.enso.projectmanager.control.core.syntax._
import org.enso.projectmanager.control.effect.syntax._ import org.enso.projectmanager.control.effect.syntax._
import org.enso.projectmanager.control.effect.{ErrorChannel, Sync} import org.enso.projectmanager.control.effect.{ErrorChannel, Sync}
import org.enso.projectmanager.data.{ import org.enso.projectmanager.data.{
@ -48,6 +48,8 @@ import org.enso.projectmanager.service.versionmanagement.RuntimeVersionManagerEr
import org.enso.projectmanager.service.versionmanagement.RuntimeVersionManagerFactory import org.enso.projectmanager.service.versionmanagement.RuntimeVersionManagerFactory
import org.enso.projectmanager.versionmanagement.DistributionConfiguration import org.enso.projectmanager.versionmanagement.DistributionConfiguration
import java.util.UUID
/** Implementation of business logic for project management. /** Implementation of business logic for project management.
* *
* @param validator a project validator * @param validator a project validator
@ -87,7 +89,16 @@ class ProjectService[
_ <- validateName(name) _ <- validateName(name)
_ <- checkIfNameExists(name) _ <- checkIfNameExists(name)
creationTime <- clock.nowInUtc() creationTime <- clock.nowInUtc()
project = Project(projectId, name, UserProject, creationTime) defaultEdition = Config.makeCompatibilityEditionFromVersion(
DefaultEnsoVersion
)
project = Project(
id = projectId,
name = name,
kind = UserProject,
created = creationTime,
edition = defaultEdition
)
path <- repo.findPathForNewProject(project).mapError(toServiceFailure) path <- repo.findPathForNewProject(project).mapError(toServiceFailure)
_ <- log.debug( _ <- log.debug(
"Found a path [{}] for a new project [{}, {}].", "Found a path [{}] for a new project [{}, {}].",
@ -275,7 +286,7 @@ class ProjectService[
} }
.mapRuntimeManagerErrors(th => .mapRuntimeManagerErrors(th =>
ProjectOpenFailed( ProjectOpenFailed(
s"Cannot install the required engine. ${th.getMessage}" s"Cannot install the required engine. $th"
) )
) )
@ -285,8 +296,9 @@ class ProjectService[
project: Project, project: Project,
missingComponentAction: MissingComponentAction missingComponentAction: MissingComponentAction
): F[ProjectServiceFailure, RunningLanguageServerInfo] = for { ): F[ProjectServiceFailure, RunningLanguageServerInfo] = for {
version <- resolveProjectVersion(project)
version <- configurationService version <- configurationService
.resolveEnsoVersion(project.engineVersion) .resolveEnsoVersion(version)
.mapError { case ConfigurationFileAccessFailure(message) => .mapError { case ConfigurationFileAccessFailure(message) =>
ProjectOpenFailed( ProjectOpenFailed(
"Could not deduce the default version to use for the project: " + "Could not deduce the default version to use for the project: " +
@ -345,15 +357,17 @@ class ProjectService[
private def resolveProjectMetadata( private def resolveProjectMetadata(
project: Project project: Project
): F[ProjectServiceFailure, ProjectMetadata] = ): F[ProjectServiceFailure, ProjectMetadata] =
configurationService for {
.resolveEnsoVersion(project.engineVersion) version <- resolveProjectVersion(project)
.mapError { case ConfigurationFileAccessFailure(message) => version <- configurationService
GlobalConfigurationAccessFailure( .resolveEnsoVersion(version)
"Could not deduce the default version to use for the project: " + .mapError { case ConfigurationFileAccessFailure(message) =>
message GlobalConfigurationAccessFailure(
) "Could not deduce the default version to use for the project: " +
} message
.map(toProjectMetadata(_, project)) )
}
} yield toProjectMetadata(version, project)
private def toProjectMetadata( private def toProjectMetadata(
engineVersion: SemVer, engineVersion: SemVer,
@ -439,4 +453,19 @@ class ProjectService[
log.debug("Project name [{}] validated by [{}].", name, validator) log.debug("Project name [{}] validated by [{}].", name, validator)
} }
private def resolveProjectVersion(
project: Project
): F[ProjectServiceFailure, EnsoVersion] =
Sync[F]
.blockingOp {
distributionConfiguration.editionManager
.resolveEngineVersion(project.edition)
.get
}
.mapError { error =>
ProjectServiceFailure.GlobalConfigurationAccessFailure(
s"Could not resolve project engine version: ${error.getMessage}"
)
}
} }

View File

@ -2,7 +2,7 @@ package org.enso.projectmanager.service.config
import io.circe.Json import io.circe.Json
import nl.gn0s1s.bump.SemVer import nl.gn0s1s.bump.SemVer
import org.enso.pkg.{DefaultEnsoVersion, EnsoVersion, SemVerEnsoVersion} import org.enso.editions.{DefaultEnsoVersion, EnsoVersion, SemVerEnsoVersion}
import org.enso.projectmanager.control.core.CovariantFlatMap import org.enso.projectmanager.control.core.CovariantFlatMap
import org.enso.projectmanager.control.effect.{ErrorChannel, Sync} import org.enso.projectmanager.control.effect.{ErrorChannel, Sync}
import org.enso.projectmanager.service.config.GlobalConfigServiceFailure.ConfigurationFileAccessFailure import org.enso.projectmanager.service.config.GlobalConfigServiceFailure.ConfigurationFileAccessFailure

View File

@ -1,7 +1,7 @@
package org.enso.projectmanager.service.config package org.enso.projectmanager.service.config
import nl.gn0s1s.bump.SemVer import nl.gn0s1s.bump.SemVer
import org.enso.pkg.EnsoVersion import org.enso.editions.EnsoVersion
/** A contract for the Global Config Service. /** A contract for the Global Config Service.
* *

View File

@ -1,17 +1,16 @@
package org.enso.projectmanager.service.versionmanagement package org.enso.projectmanager.service.versionmanagement
import java.util.UUID import java.util.UUID
import akka.actor.ActorRef import akka.actor.ActorRef
import com.typesafe.scalalogging.Logger import com.typesafe.scalalogging.Logger
import nl.gn0s1s.bump.SemVer import nl.gn0s1s.bump.SemVer
import org.enso.cli.task.{ProgressListener, TaskProgress} import org.enso.cli.task.{ProgressListener, TaskProgress}
import org.enso.distribution.locking.Resource
import org.enso.projectmanager.data.ProgressUnit import org.enso.projectmanager.data.ProgressUnit
import org.enso.runtimeversionmanager.components.{ import org.enso.runtimeversionmanager.components.{
GraalVMVersion, GraalVMVersion,
RuntimeVersionManagementUserInterface RuntimeVersionManagementUserInterface
} }
import org.enso.runtimeversionmanager.locking.Resource
import scala.util.{Failure, Success, Try} import scala.util.{Failure, Success, Try}

View File

@ -2,11 +2,11 @@ package org.enso.projectmanager.service.versionmanagement
import nl.gn0s1s.bump.SemVer import nl.gn0s1s.bump.SemVer
import org.enso.cli.task.TaskProgress import org.enso.cli.task.TaskProgress
import org.enso.distribution.locking.Resource
import org.enso.runtimeversionmanager.components.{ import org.enso.runtimeversionmanager.components.{
GraalVMVersion, GraalVMVersion,
RuntimeVersionManagementUserInterface RuntimeVersionManagementUserInterface
} }
import org.enso.runtimeversionmanager.locking.Resource
/** A simple [[RuntimeVersionManagementUserInterface]] that does not allow to /** A simple [[RuntimeVersionManagementUserInterface]] that does not allow to
* install any versions and does not track any progress. * install any versions and does not track any progress.

View File

@ -1,7 +1,11 @@
package org.enso.projectmanager.versionmanagement package org.enso.projectmanager.versionmanagement
import com.typesafe.scalalogging.LazyLogging import com.typesafe.scalalogging.LazyLogging
import org.enso.runtimeversionmanager.Environment import org.enso.distribution.{DistributionManager, EditionManager, Environment}
import org.enso.distribution.locking.{
ResourceManager,
ThreadSafeFileLockManager
}
import org.enso.runtimeversionmanager.components.{ import org.enso.runtimeversionmanager.components.{
GraalVMComponentConfiguration, GraalVMComponentConfiguration,
InstallerKind, InstallerKind,
@ -10,14 +14,7 @@ import org.enso.runtimeversionmanager.components.{
RuntimeVersionManagementUserInterface, RuntimeVersionManagementUserInterface,
RuntimeVersionManager RuntimeVersionManager
} }
import org.enso.runtimeversionmanager.distribution.{ import org.enso.runtimeversionmanager.distribution.TemporaryDirectoryManager
DistributionManager,
TemporaryDirectoryManager
}
import org.enso.runtimeversionmanager.locking.{
ResourceManager,
ThreadSafeFileLockManager
}
import org.enso.runtimeversionmanager.releases.ReleaseProvider import org.enso.runtimeversionmanager.releases.ReleaseProvider
import org.enso.runtimeversionmanager.releases.engine.{ import org.enso.runtimeversionmanager.releases.engine.{
EngineRelease, EngineRelease,
@ -51,6 +48,9 @@ object DefaultDistributionConfiguration
/** @inheritdoc */ /** @inheritdoc */
lazy val resourceManager = new ResourceManager(lockManager) lazy val resourceManager = new ResourceManager(lockManager)
/** @inheritdoc */
lazy val editionManager = EditionManager(distributionManager)
/** @inheritdoc */ /** @inheritdoc */
lazy val temporaryDirectoryManager = lazy val temporaryDirectoryManager =
new TemporaryDirectoryManager(distributionManager, resourceManager) new TemporaryDirectoryManager(distributionManager, resourceManager)

View File

@ -1,15 +1,12 @@
package org.enso.projectmanager.versionmanagement package org.enso.projectmanager.versionmanagement
import org.enso.runtimeversionmanager.Environment import org.enso.distribution.locking.ResourceManager
import org.enso.distribution.{DistributionManager, EditionManager, Environment}
import org.enso.runtimeversionmanager.components.{ import org.enso.runtimeversionmanager.components.{
RuntimeVersionManagementUserInterface, RuntimeVersionManagementUserInterface,
RuntimeVersionManager RuntimeVersionManager
} }
import org.enso.runtimeversionmanager.distribution.{ import org.enso.runtimeversionmanager.distribution.TemporaryDirectoryManager
DistributionManager,
TemporaryDirectoryManager
}
import org.enso.runtimeversionmanager.locking.ResourceManager
import org.enso.runtimeversionmanager.releases.ReleaseProvider import org.enso.runtimeversionmanager.releases.ReleaseProvider
import org.enso.runtimeversionmanager.releases.engine.EngineRelease import org.enso.runtimeversionmanager.releases.engine.EngineRelease
import org.enso.runtimeversionmanager.runner.JVMSettings import org.enso.runtimeversionmanager.runner.JVMSettings
@ -31,6 +28,9 @@ trait DistributionConfiguration {
/** A [[ResourceManager]] instance. */ /** A [[ResourceManager]] instance. */
def resourceManager: ResourceManager def resourceManager: ResourceManager
/** An [[EditionManager]] instance. */
def editionManager: EditionManager
/** A [[TemporaryDirectoryManager]] instance. */ /** A [[TemporaryDirectoryManager]] instance. */
def temporaryDirectoryManager: TemporaryDirectoryManager def temporaryDirectoryManager: TemporaryDirectoryManager

View File

@ -4,13 +4,13 @@ import java.io.File
import java.nio.file.{Files, Path} import java.nio.file.{Files, Path}
import java.time.{OffsetDateTime, ZoneOffset} import java.time.{OffsetDateTime, ZoneOffset}
import java.util.UUID import java.util.UUID
import akka.testkit.TestActors.blackholeProps import akka.testkit.TestActors.blackholeProps
import akka.testkit._ import akka.testkit._
import io.circe.Json import io.circe.Json
import io.circe.parser.parse import io.circe.parser.parse
import nl.gn0s1s.bump.SemVer import nl.gn0s1s.bump.SemVer
import org.apache.commons.io.FileUtils import org.apache.commons.io.FileUtils
import org.enso.distribution.OS
import org.enso.jsonrpc.test.JsonRpcServerTestKit import org.enso.jsonrpc.test.JsonRpcServerTestKit
import org.enso.jsonrpc.{ClientControllerFactory, Protocol} import org.enso.jsonrpc.{ClientControllerFactory, Protocol}
import org.enso.loggingservice.printers.StderrPrinterWithColors import org.enso.loggingservice.printers.StderrPrinterWithColors
@ -39,7 +39,6 @@ import org.enso.projectmanager.service.{
ProjectService ProjectService
} }
import org.enso.projectmanager.test.{ObservableGenerator, ProgrammableClock} import org.enso.projectmanager.test.{ObservableGenerator, ProgrammableClock}
import org.enso.runtimeversionmanager.OS
import org.enso.runtimeversionmanager.test.FakeReleases import org.enso.runtimeversionmanager.test.FakeReleases
import org.scalatest.BeforeAndAfterAll import org.scalatest.BeforeAndAfterAll
import pureconfig.ConfigSource import pureconfig.ConfigSource

View File

@ -5,7 +5,7 @@ import akka.testkit.TestDuration
import io.circe.Json import io.circe.Json
import io.circe.syntax._ import io.circe.syntax._
import io.circe.literal._ import io.circe.literal._
import org.enso.pkg.SemVerJson._ import org.enso.editions.SemVerJson._
import io.circe.parser.parse import io.circe.parser.parse
import nl.gn0s1s.bump.SemVer import nl.gn0s1s.bump.SemVer
import org.enso.projectmanager.data.{MissingComponentAction, Socket} import org.enso.projectmanager.data.{MissingComponentAction, Socket}

View File

@ -1,7 +1,9 @@
package org.enso.projectmanager package org.enso.projectmanager
import java.nio.file.Path import org.enso.distribution.{DistributionManager, EditionManager}
import org.enso.distribution.locking.ResourceManager
import java.nio.file.Path
import org.enso.projectmanager.versionmanagement.DistributionConfiguration import org.enso.projectmanager.versionmanagement.DistributionConfiguration
import org.enso.runtimeversionmanager.components.{ import org.enso.runtimeversionmanager.components.{
GraalVMComponentConfiguration, GraalVMComponentConfiguration,
@ -9,11 +11,7 @@ import org.enso.runtimeversionmanager.components.{
RuntimeVersionManagementUserInterface, RuntimeVersionManagementUserInterface,
RuntimeVersionManager RuntimeVersionManager
} }
import org.enso.runtimeversionmanager.distribution.{ import org.enso.runtimeversionmanager.distribution.TemporaryDirectoryManager
DistributionManager,
TemporaryDirectoryManager
}
import org.enso.runtimeversionmanager.locking.ResourceManager
import org.enso.runtimeversionmanager.releases.engine.{ import org.enso.runtimeversionmanager.releases.engine.{
EngineRelease, EngineRelease,
EngineReleaseProvider EngineReleaseProvider
@ -66,6 +64,8 @@ class TestDistributionConfiguration(
lazy val resourceManager = new ResourceManager(lockManager) lazy val resourceManager = new ResourceManager(lockManager)
lazy val editionManager: EditionManager = EditionManager(distributionManager)
lazy val temporaryDirectoryManager = lazy val temporaryDirectoryManager =
new TemporaryDirectoryManager(distributionManager, resourceManager) new TemporaryDirectoryManager(distributionManager, resourceManager)

View File

@ -6,7 +6,7 @@ import nl.gn0s1s.bump.SemVer
import org.enso.projectmanager.data.MissingComponentAction import org.enso.projectmanager.data.MissingComponentAction
import org.enso.projectmanager.{BaseServerSpec, ProjectManagementOps} import org.enso.projectmanager.{BaseServerSpec, ProjectManagementOps}
import org.enso.testkit.RetrySpec import org.enso.testkit.RetrySpec
import org.enso.pkg.SemVerJson._ import org.enso.editions.SemVerJson._
class ProjectCreateMissingComponentsSpec class ProjectCreateMissingComponentsSpec
extends BaseServerSpec extends BaseServerSpec

View File

@ -7,7 +7,7 @@ import java.util.UUID
import io.circe.literal._ import io.circe.literal._
import nl.gn0s1s.bump.SemVer import nl.gn0s1s.bump.SemVer
import org.apache.commons.io.FileUtils import org.apache.commons.io.FileUtils
import org.enso.pkg.SemVerJson._ import org.enso.editions.SemVerJson._
import org.enso.projectmanager.{BaseServerSpec, ProjectManagementOps} import org.enso.projectmanager.{BaseServerSpec, ProjectManagementOps}
import org.enso.testkit.{FlakySpec, RetrySpec} import org.enso.testkit.{FlakySpec, RetrySpec}

Some files were not shown because too many files have changed in this diff Show More