Dynamically Loading Libraries (#1826)

This commit is contained in:
Radosław Waśko 2021-07-06 00:27:14 +02:00 committed by GitHub
parent 8d71145d57
commit e58b5eb81d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
77 changed files with 1803 additions and 493 deletions

View File

@ -7,6 +7,10 @@
project ([#1806](https://github.com/enso-org/enso/pull/1806)).
- Fixed a bug where unresolved imports would crash the compiler
([#1822](https://github.com/enso-org/enso/pull/1822)).
- Implemented the ability to dynamically load local libraries
([#1826](https://github.com/enso-org/enso/pull/1826)). Currently, it only
supports the loading of local libraries, but will be integrated with the
editions system soon.
## Tooling

View File

@ -652,6 +652,18 @@ lazy val `logging-service` = project
.dependsOn(`akka-native`)
.dependsOn(`logging-utils`)
lazy val `logging-truffle-connector` = project
.in(file("lib/scala/logging-truffle-connector"))
.settings(
version := "0.1",
libraryDependencies ++= Seq(
"org.slf4j" % "slf4j-api" % slf4jVersion,
"org.graalvm.truffle" % "truffle-api" % graalVersion % "provided"
)
)
.dependsOn(`logging-utils`)
.dependsOn(`polyglot-api`)
lazy val cli = project
.in(file("lib/scala/cli"))
.configs(Test)
@ -1084,8 +1096,10 @@ lazy val runtime = (project in file("engine/runtime"))
.dependsOn(`polyglot-api`)
.dependsOn(`text-buffer`)
.dependsOn(searcher)
.dependsOn(`library-manager`)
.dependsOn(testkit % Test)
.dependsOn(`logging-utils`)
.dependsOn(`logging-truffle-connector`)
.dependsOn(`docs-generator`)
/* Note [Unmanaged Classpath]

View File

@ -26,11 +26,6 @@ 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`.
'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.
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`.

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
*
@ -43,3 +27,19 @@
* See the License for the specific language governing permissions and
* 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");
* 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");
* 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");
* 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) 2009-2020 Lightbend Inc. <https://www.lightbend.com>
*/

View File

@ -15,8 +15,6 @@
* 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-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
* 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");
* 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");
* you may not use this file except in compliance with the License.

View File

@ -1,24 +0,0 @@
/**
* Copyright (c) 2004-2011 QOS.ch
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
*/

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) 2009-2020 Lightbend Inc. <https://www.lightbend.com>
*/

View File

@ -15,8 +15,6 @@
* 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-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
* 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");
* 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");
* you may not use this file except in compliance with the License.

View File

@ -14,3 +14,4 @@ Documents in this section describe Enso's library ecosystem.
organizes library versioning.
- [**Repositories:**](./repositories.md) Information on the structure of
repositories providing Enso libraries and Editions.
- [**Sharing Libraries:**](./sharing.md) Information on how to share libraries.

70
docs/libraries/sharing.md Normal file
View File

@ -0,0 +1,70 @@
---
layout: developer-doc
title: Sharing Libraries
category: libraries
tags: [libraries, editions, sharing]
order: 3
---
# Sharing Libraries
This document explains how users can share Enso libraries.
<!-- MarkdownTOC levels="2,3" autolink="true" -->
- [Sharing Privately](#sharing-privately)
- [Publishing](#publishing)
<!-- /MarkdownTOC -->
## Sharing Privately
To prepare the project for sharing, make sure that it has a proper `namespace`
field set in `package.yaml`. It should be set to something unique, like your
username.
> **NOTE**: The field `namespace` is a temporary workaround and in the near
> future it will be deprecated and handled mostly automatically. For now however
> you need to set it properly.
To share an Enso library, all you need to do is to package the project into an
archive (for example ZIP) and share it (through e-mail, cloud drive services
etc.) with your peers. Now to be able to use the library that was shared with
you, you need to extract it to the directory
`~/enso/libraries/<namespace>/<Project_Name>` (where on Windows `~` should be
interpreted as your user home directory). To make sure that the library is
extracted correctly, make sure that under the path
`~/enso/libraries/<namespace>/<Project_Name>/package.yaml` and that its
`namespace` field has the same value as the name of the `<namespace>` directory.
> The below step is not necessary yet, but it will be needed once the editions
> system is fully integrated, so it is better to perform it for forwards
> compatibility.
Now you need to set up your project properly to be able to use this unpublished
library. The simplest way to do that is to set `prefer-local-libraries` in your
project's `package.yaml` to `true`. This will make all libraries from
`~/enso/libraries` take precedence over published libraries set-up in the
edition. Alternatively, if you do not want to override all libraries, but only
some of them, you can add a local library override, by adding a proper entry in
the `libraries` section of the `edition` in your project's `package.yaml`, like
shown below:
```yaml
edition:
(...)
libraries:
- name: <namespace>.<Project_Name>
repository: local
```
Now, you can use your library by adding a proper import to your project:
```
import <namespace>.<Project_Name>
```
## Publishing
> Soon it will be possible to share the libraries through the Marketplace, but
> it is still a work in progress.

View File

@ -200,7 +200,8 @@ class MainModule(serverConfig: LanguageServerConfig, logLevel: LogLevel) {
.allowAllAccess(true)
.allowExperimentalOptions(true)
.option(RuntimeServerInfo.ENABLE_OPTION, "true")
.option(RuntimeOptions.PACKAGES_PATH, serverConfig.contentRootPath)
.option(RuntimeOptions.INTERACTIVE_MODE, "true")
.option(RuntimeOptions.PROJECT_ROOT, serverConfig.contentRootPath)
.option(
RuntimeOptions.LOG_LEVEL,
JavaLoggingLogHandler.getJavaLogLevelFor(logLevel).getName

View File

@ -7,6 +7,7 @@ import org.enso.languageserver.filemanager.ContentRootManagerActor.ContentRoots
import org.enso.languageserver.filemanager.ContentRootManagerProtocol._
import org.enso.languageserver.monitoring.MonitoringProtocol.{Ping, Pong}
import org.enso.languageserver.util.UnhandledLogging
import org.enso.logger.masking.MaskedPath
import org.enso.polyglot.runtime.Runtime.Api
import java.io.File
@ -57,17 +58,22 @@ class ContentRootManagerActor(config: Config)
sender() ! ContentRootsAddedNotification(contentRoots.toList)
context.become(mainStage(contentRoots, subscribers + sender()))
case Api.LibraryLoaded(libraryName, libraryVersion, rootPath) =>
case Api.LibraryLoaded(namespace, name, version, rootPath) =>
val libraryRoot = ContentRootWithFile(
ContentRoot.Library(
id = UUID.randomUUID(),
namespace = libraryName.namespace,
name = libraryName.name,
version = libraryVersion.toString
namespace = namespace,
name = name,
version = version.toString
),
file = rootPath.getCanonicalFile
)
logger.trace(
s"Library root for [$namespace.$name:$version] added at " +
s"[${MaskedPath(rootPath.toPath).applyMasking()}]."
)
subscribers.foreach { subscriber =>
subscriber ! ContentRootsAddedNotification(List(libraryRoot))
}

View File

@ -3,7 +3,6 @@ package org.enso.languageserver.filemanager
import akka.actor.{ActorRef, ActorSystem}
import akka.testkit.{TestDuration, TestKit, TestProbe}
import org.apache.commons.lang3.SystemUtils
import org.enso.editions.{LibraryName, LibraryVersion}
import org.enso.languageserver.data._
import org.enso.languageserver.filemanager.ContentRootManagerProtocol.{
ContentRootsAddedNotification,
@ -11,11 +10,11 @@ import org.enso.languageserver.filemanager.ContentRootManagerProtocol.{
}
import org.enso.polyglot.runtime.Runtime.Api
import org.enso.testkit.EitherValue
import org.scalatest.{Inside, OptionValues}
import org.scalatest.concurrent.Futures
import org.scalatest.concurrent.ScalaFutures.convertScalaFuture
import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AnyWordSpecLike
import org.scalatest.{Inside, OptionValues}
import java.io.File
import java.nio.file.{Path => JPath}
@ -87,12 +86,10 @@ class ContentRootManagerSpec
fsRoots should not be empty
}
val libraryName = LibraryName("Foo", "Bar")
val libraryVersion = LibraryVersion.Local
val rootPath = new File("foobar")
val rootPath = new File("foobar")
system.eventStream.publish(
Api.LibraryLoaded(libraryName, libraryVersion, rootPath)
Api.LibraryLoaded("Foo", "Bar", "local", rootPath)
)
inside(subscriberProbe.receiveOne(2.seconds.dilated)) {

View File

@ -2,11 +2,8 @@ package org.enso.languageserver.websocket.json
import io.circe.literal._
import io.circe.parser.parse
import nl.gn0s1s.bump.SemVer
import org.apache.commons.io.FileUtils
import org.bouncycastle.util.encoders.Hex
import org.enso.editions.Editions.Repository
import org.enso.editions.{LibraryName, LibraryVersion}
import org.enso.languageserver.data._
import org.enso.polyglot.runtime.Runtime.Api
import org.enso.testkit.RetrySpec
@ -1792,15 +1789,10 @@ class FileManagerTest extends BaseServerTest with RetrySpec {
"Content root management" must {
"notify the IDE when a new root is added" in {
val client = getInitialisedWsClient()
val repo = Repository.make("example", "https://example.com/").get
val libraryName = LibraryName("Foo", "Bar")
val libraryVersion = LibraryVersion.Published(SemVer(1, 2, 3), repo)
val rootPath = new File("foobar")
val client = getInitialisedWsClient()
val rootPath = new File("foobar")
system.eventStream.publish(
Api.LibraryLoaded(libraryName, libraryVersion, rootPath)
Api.LibraryLoaded("Foo", "Bar", "1.2.3", rootPath)
)
val parsed = parse(client.expectMessage())

View File

@ -51,6 +51,7 @@ case class Launcher(cliOptions: GlobalCLIOptions) {
private lazy val runner =
new LauncherRunner(
projectManager,
distributionManager,
configurationManager,
componentsManager,
editionManager,

View File

@ -2,7 +2,7 @@ package org.enso.launcher.components
import akka.http.scaladsl.model.Uri
import nl.gn0s1s.bump.SemVer
import org.enso.distribution.{EditionManager, Environment}
import org.enso.distribution.{DistributionManager, EditionManager, Environment}
import org.enso.launcher.project.ProjectManager
import org.enso.loggingservice.LogLevel
import org.enso.runtimeversionmanager.components.RuntimeVersionManager
@ -17,6 +17,7 @@ import scala.util.Try
*/
class LauncherRunner(
projectManager: ProjectManager,
distributionManager: DistributionManager,
configurationManager: GlobalConfigurationManager,
componentsManager: RuntimeVersionManager,
editionManager: EditionManager,
@ -24,6 +25,7 @@ class LauncherRunner(
loggerConnection: Future[Option[Uri]]
) extends Runner(
componentsManager,
distributionManager,
configurationManager,
editionManager,
environment,

View File

@ -133,7 +133,7 @@ class DistributionInstaller(
throw InstallationError(
s"${installed.configDirectory} already exists but is not a " +
s"directory. Please remove it or change the installation " +
s"location by setting `${installed.ENSO_CONFIG_DIRECTORY}`."
s"location by setting `${manager.ENSO_CONFIG_DIRECTORY}`."
)
}
}

View File

@ -164,7 +164,7 @@ class DistributionUninstaller(
val remaining =
FileSystem.listDirectory(manager.paths.config).map(_.getFileName.toString)
handleRemainingFiles(
manager.LocallyInstalledDirectories.ENSO_CONFIG_DIRECTORY,
manager.ENSO_CONFIG_DIRECTORY,
manager.paths.config,
remaining
)
@ -244,7 +244,7 @@ class DistributionUninstaller(
.toSet -- ignoredFiles
if (remainingFiles.nonEmpty) {
handleRemainingFiles(
manager.LocallyInstalledDirectories.ENSO_DATA_DIRECTORY,
manager.ENSO_DATA_DIRECTORY,
dataRoot.toAbsolutePath.normalize,
remainingFiles.toSeq
)

View File

@ -37,6 +37,7 @@ class LauncherRunnerSpec extends RuntimeVersionManagerTest {
val runner =
new LauncherRunner(
projectManager,
distributionManager,
configurationManager,
componentsManager,
editionManager,

View File

@ -8,10 +8,10 @@ import org.graalvm.options.OptionKey;
/** Class representing runtime options supported by the Enso engine. */
public class RuntimeOptions {
public static final String PACKAGES_PATH = optionName("packagesPath");
public static final OptionKey<String> PACKAGES_PATH_KEY = new OptionKey<>("");
private static final OptionDescriptor PACKAGES_PATH_DESCRIPTOR =
OptionDescriptor.newBuilder(PACKAGES_PATH_KEY, PACKAGES_PATH).build();
public static final String PROJECT_ROOT = optionName("projectRoot");
public static final OptionKey<String> PROJECT_ROOT_KEY = new OptionKey<>("");
private static final OptionDescriptor PROJECT_ROOT_DESCRIPTOR =
OptionDescriptor.newBuilder(PROJECT_ROOT_KEY, PROJECT_ROOT).build();
public static final String STRICT_ERRORS = optionName("strictErrors");
public static final OptionKey<Boolean> STRICT_ERRORS_KEY = new OptionKey<>(false);
@ -28,6 +28,11 @@ public class RuntimeOptions {
private static final OptionDescriptor LOG_LEVEL_DESCRIPTOR =
OptionDescriptor.newBuilder(LOG_LEVEL_KEY, LOG_LEVEL).build();
public static final String INTERACTIVE_MODE = interpreterOptionName("interactive");
public static final OptionKey<Boolean> INTERACTIVE_MODE_KEY = new OptionKey<>(false);
public static final OptionDescriptor INTERACTIVE_MODE_DESCRIPTOR =
OptionDescriptor.newBuilder(INTERACTIVE_MODE_KEY, INTERACTIVE_MODE).build();
public static final String INTERPRETER_SEQUENTIAL_COMMAND_EXECUTION =
interpreterOptionName("sequentialCommandExecution");
public static final OptionKey<Boolean> INTERPRETER_SEQUENTIAL_COMMAND_EXECUTION_KEY =
@ -41,22 +46,30 @@ public class RuntimeOptions {
public static final String ENABLE_PROJECT_SUGGESTIONS = optionName("enableProjectSuggestions");
public static final OptionKey<Boolean> ENABLE_PROJECT_SUGGESTIONS_KEY = new OptionKey<>(true);
private static final OptionDescriptor ENABLE_PROJECT_SUGGESTIONS_DESCRIPTOR =
OptionDescriptor.newBuilder(ENABLE_PROJECT_SUGGESTIONS_KEY, ENABLE_PROJECT_SUGGESTIONS).build();
OptionDescriptor.newBuilder(ENABLE_PROJECT_SUGGESTIONS_KEY, ENABLE_PROJECT_SUGGESTIONS)
.build();
public static final String ENABLE_GLOBAL_SUGGESTIONS = optionName("enableGlobalSuggestions");
public static final OptionKey<Boolean> ENABLE_GLOBAL_SUGGESTIONS_KEY = new OptionKey<>(true);
private static final OptionDescriptor ENABLE_GLOBAL_SUGGESTIONS_DESCRIPTOR =
OptionDescriptor.newBuilder(ENABLE_GLOBAL_SUGGESTIONS_KEY, ENABLE_GLOBAL_SUGGESTIONS).build();
public static final String PRELOADED_PACKAGES_PATHS = optionName("preloadedPackagesPaths");
public static final OptionKey<String> PRELOADED_PACKAGES_PATHS_KEY = new OptionKey<>("");
private static final OptionDescriptor PRELOADED_PACKAGES_PATHS_DESCRIPTOR =
OptionDescriptor.newBuilder(PRELOADED_PACKAGES_PATHS_KEY, PRELOADED_PACKAGES_PATHS).build();
public static final OptionDescriptors OPTION_DESCRIPTORS =
OptionDescriptors.create(
Arrays.asList(
PACKAGES_PATH_DESCRIPTOR,
PROJECT_ROOT_DESCRIPTOR,
STRICT_ERRORS_DESCRIPTOR,
LOG_LEVEL_DESCRIPTOR,
DISABLE_INLINE_CACHES_DESCRIPTOR,
ENABLE_PROJECT_SUGGESTIONS_DESCRIPTOR,
ENABLE_GLOBAL_SUGGESTIONS_DESCRIPTOR,
INTERACTIVE_MODE_DESCRIPTOR,
PRELOADED_PACKAGES_PATHS_DESCRIPTOR,
INTERPRETER_SEQUENTIAL_COMMAND_EXECUTION_DESCRIPTOR));
/**

View File

@ -7,7 +7,6 @@ import com.fasterxml.jackson.module.scala.{
DefaultScalaModule,
ScalaObjectMapper
}
import org.enso.editions.{LibraryName, LibraryVersion}
import org.enso.logger.masking.{MaskedPath, MaskedString, ToLogString}
import org.enso.polyglot.Suggestion
import org.enso.polyglot.data.{Tree, TypeGraph}
@ -1341,14 +1340,16 @@ object Runtime {
/** Signals that a new library has been imported, which means its content
* root should be registered.
*
* @param namespace namespace of the loaded library
* @param name name of the loaded library
* @param version library version that was selected
* @param location location on disk of the project root belonging to the
* loaded library
*/
case class LibraryLoaded(
name: LibraryName,
version: LibraryVersion,
namespace: String,
name: String,
version: String,
location: File
) extends ApiNotification

View File

@ -18,7 +18,7 @@ class ModuleManagementTest extends AnyFlatSpec with Matchers {
.newBuilder(LanguageInfo.ID)
.allowExperimentalOptions(true)
.allowAllAccess(true)
.option(RuntimeOptions.PACKAGES_PATH, pkg.root.getAbsolutePath)
.option(RuntimeOptions.PROJECT_ROOT, pkg.root.getAbsolutePath)
.option(RuntimeOptions.STRICT_ERRORS, "true")
.build()
)

View File

@ -17,7 +17,8 @@ class ContextFactory {
/** Creates a new Graal polyglot context.
*
* @param packagesPath Enso packages path
* @param projectRoot root of the project the interpreter is being run in
* (or empty if ran outside of any projects)
* @param in the input stream for standard in
* @param out the output stream for standard out
* @param repl the Repl manager to use for this context
@ -26,7 +27,7 @@ class ContextFactory {
* @return configured Context instance
*/
def create(
packagesPath: String = "",
projectRoot: String = "",
in: InputStream,
out: OutputStream,
repl: Repl,
@ -37,7 +38,7 @@ class ContextFactory {
.newBuilder()
.allowExperimentalOptions(true)
.allowAllAccess(true)
.option(RuntimeOptions.PACKAGES_PATH, packagesPath)
.option(RuntimeOptions.PROJECT_ROOT, projectRoot)
.option(RuntimeOptions.STRICT_ERRORS, strictErrors.toString)
.option(DebugServerInfo.ENABLE_OPTION, "true")
.option("js.foreign-object-prototype", "true")

View File

@ -284,7 +284,7 @@ object Main {
exitFail()
}
val projectMode = file.isDirectory
val packagePath =
val projectRoot =
if (projectMode) {
projectPath match {
case Some(inProject) if inProject != path =>
@ -299,7 +299,7 @@ object Main {
file.getAbsolutePath
} else projectPath.getOrElse("")
val context = new ContextFactory().create(
packagePath,
projectRoot,
System.in,
System.out,
Repl(TerminalIO()),
@ -426,10 +426,10 @@ object Main {
|$mainMethodName = Debug.breakpoint
|""".stripMargin
val replModuleName = "Internal_Repl_Module___"
val packagePath = projectPath.getOrElse("")
val projectRoot = projectPath.getOrElse("")
val context =
new ContextFactory().create(
packagePath,
projectRoot,
System.in,
System.out,
Repl(TerminalIO()),

View File

@ -10,6 +10,9 @@ import com.oracle.truffle.api.instrumentation.StandardTags;
import com.oracle.truffle.api.nodes.RootNode;
import org.enso.interpreter.epb.EpbLanguage;
import org.enso.interpreter.instrument.IdExecutionInstrument;
import org.enso.interpreter.instrument.NotificationHandler;
import org.enso.interpreter.instrument.NotificationHandler.Forwarder;
import org.enso.interpreter.instrument.NotificationHandler.TextMode$;
import org.enso.interpreter.node.ProgramRootNode;
import org.enso.interpreter.runtime.Context;
import org.enso.interpreter.runtime.tag.IdentifiedTag;
@ -60,11 +63,18 @@ public final class Language extends TruffleLanguage<Context> {
*/
@Override
protected Context createContext(Env env) {
Context context = new Context(this, getLanguageHome(), env);
var notificationHandler = new Forwarder();
boolean isInteractiveMode = env.getOptions().get(RuntimeOptions.INTERACTIVE_MODE_KEY);
boolean isTextMode = !isInteractiveMode;
if (isTextMode) {
notificationHandler.addListener(TextMode$.MODULE$);
}
Context context = new Context(this, getLanguageHome(), env, notificationHandler);
InstrumentInfo idValueListenerInstrument =
env.getInstruments().get(IdExecutionInstrument.INSTRUMENT_ID);
idExecutionInstrument = env.lookup(idValueListenerInstrument, IdExecutionInstrument.class);
env.registerService(new ExecutionService(context, idExecutionInstrument));
env.registerService(new ExecutionService(context, idExecutionInstrument, notificationHandler));
return context;
}

View File

@ -5,22 +5,42 @@ import com.oracle.truffle.api.TruffleLanguage;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import org.enso.polyglot.RuntimeOptions;
public class OptionsHelper {
/**
* Gets the list of locations of known modules that can be imported in the current run.
* Gets the location of the project that is the context of the current run.
*
* @param env the current run environment
* @return the list of locations of known modules that can be imported in the current run
* @return the project path (can be empty if running outside of the project)
*/
public static List<TruffleFile> getPackagesPaths(TruffleLanguage.Env env) {
if (env.getOptions().get(RuntimeOptions.PACKAGES_PATH_KEY).equals("")) {
public static Optional<TruffleFile> getProjectRoot(TruffleLanguage.Env env) {
String option = env.getOptions().get(RuntimeOptions.PROJECT_ROOT_KEY);
if (option.equals("")) {
return Optional.empty();
} else {
return Optional.of(env.getInternalTruffleFile(option));
}
}
/**
* Gets the list of locations of packages that should be preloaded.
*
* <p>This is meant mainly for testing purposes, as normally package locations are handled by
* environment variables, see {@link org.enso.distribution.DistributionManager}.
*
* <p>It will also be slightly repurposed after integrating with editions and once the standard
* library directory structure is upgraded to the new format.
*/
public static List<TruffleFile> getPreloadedPackagesPaths(TruffleLanguage.Env env) {
String option = env.getOptions().get(RuntimeOptions.PRELOADED_PACKAGES_PATHS_KEY);
if (option.equals("")) {
return Collections.emptyList();
} else {
return Arrays.stream(
env.getOptions().get(RuntimeOptions.PACKAGES_PATH_KEY).split(env.getPathSeparator()))
option.split(env.getPathSeparator()))
.map(env::getInternalTruffleFile)
.collect(Collectors.toList());
}

View File

@ -4,35 +4,34 @@ import com.oracle.truffle.api.CompilerDirectives.CompilationFinal;
import com.oracle.truffle.api.TruffleFile;
import com.oracle.truffle.api.TruffleLanguage;
import com.oracle.truffle.api.TruffleLanguage.Env;
import com.oracle.truffle.api.TruffleLogger;
import java.io.BufferedReader;
import java.io.File;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import java.util.function.Function;
import java.util.stream.Collectors;
import org.enso.compiler.Compiler;
import org.enso.compiler.PackageRepository;
import org.enso.compiler.data.CompilerConfig;
import org.enso.home.HomeManager;
import org.enso.interpreter.Language;
import org.enso.interpreter.OptionsHelper;
import org.enso.interpreter.instrument.NotificationHandler;
import org.enso.interpreter.runtime.builtin.Builtins;
import org.enso.interpreter.runtime.callable.atom.AtomConstructor;
import org.enso.interpreter.runtime.scope.TopLevelScope;
import org.enso.interpreter.runtime.util.ShadowedPackage;
import org.enso.interpreter.runtime.util.TruffleFileSystem;
import org.enso.interpreter.util.ScalaConversions;
import org.enso.pkg.Package;
import org.enso.pkg.PackageManager;
import org.enso.pkg.QualifiedName;
import org.enso.polyglot.LanguageInfo;
import org.enso.polyglot.RuntimeOptions;
import scala.jdk.javaapi.CollectionConverters;
/**
* The language context is the internal state of the language that is associated with each thread in
@ -42,28 +41,32 @@ public class Context {
private final Language language;
private final Env environment;
private final Compiler compiler;
private @CompilationFinal Compiler compiler;
private final PrintStream out;
private final PrintStream err;
private final InputStream in;
private final BufferedReader inReader;
private List<Package<TruffleFile>> packages;
private @CompilationFinal PackageRepository packageRepository;
private @CompilationFinal TopLevelScope topScope;
private final ThreadManager threadManager;
private final ResourceManager resourceManager;
private final boolean isCachingDisabled;
private final Builtins builtins;
private final String home;
private final List<ShadowedPackage> shadowedPackages;
private final CompilerConfig compilerConfig;
private final NotificationHandler notificationHandler;
private final TruffleLogger logger = TruffleLogger.getLogger(LanguageInfo.ID, Context.class);
/**
* Creates a new Enso context.
*
* @param language the language identifier
* @param home language home
* @param environment the execution environment of the {@link TruffleLanguage}
* @param notificationHandler a handler for notifications
*/
public Context(Language language, String home, Env environment) {
public Context(
Language language, String home, Env environment, NotificationHandler notificationHandler) {
this.language = language;
this.environment = environment;
this.out = new PrintStream(environment.out());
@ -75,68 +78,59 @@ public class Context {
this.isCachingDisabled = environment.getOptions().get(RuntimeOptions.DISABLE_INLINE_CACHES_KEY);
this.compilerConfig = new CompilerConfig(false, true);
this.home = home;
this.shadowedPackages = new ArrayList<>();
builtins = new Builtins(this);
this.compiler =
new Compiler(this, builtins, new PackageRepository.Default(this), compilerConfig);
this.builtins = new Builtins(this);
this.notificationHandler = notificationHandler;
}
/** Perform expensive initialization logic for the context. */
public void initialize() {
TruffleFileSystem fs = new TruffleFileSystem();
HashMap<String, Package<TruffleFile>> packageMap = new HashMap<>();
packages = new ArrayList<>();
if (home != null) {
HomeManager<TruffleFile> homeManager =
new HomeManager<>(environment.getInternalTruffleFile(home), fs);
packageMap.putAll(
homeManager.loadStdLib().collect(Collectors.toMap((Package::name), Function.identity())));
}
PackageManager<TruffleFile> packageManager = new PackageManager<>(fs);
List<TruffleFile> packagePaths = OptionsHelper.getPackagesPaths(environment);
// Add user packages one-by-one, shadowing previously added packages. It assumes that the
// standard library packages will not clash. In the future, we should be able to disambiguate
// packages that clash.
for (var packagePath : packagePaths) {
Optional<Package<TruffleFile>> asPackage =
Optional<TruffleFile> projectRoot = OptionsHelper.getProjectRoot(environment);
Optional<Package<TruffleFile>> projectPackage =
projectRoot.flatMap(
file -> {
var result = packageManager.fromDirectory(projectRoot.get());
if (result.isEmpty()) {
logger.warning("Could not load the project root package.");
}
return ScalaConversions.asJava(result);
});
packageRepository =
PackageRepository.makeLegacyRepository(
RuntimeDistributionManager$.MODULE$, this, builtins, notificationHandler);
topScope = new TopLevelScope(builtins, packageRepository);
this.compiler = new Compiler(this, builtins, packageRepository, compilerConfig);
projectPackage.ifPresent(
pkg -> packageRepository.registerMainProjectPackage(pkg.libraryName(), pkg));
List<Package<TruffleFile>> packagesToPreload = new ArrayList<>();
// TODO [RW] This preloading mechanism should be replaced by prepending this special path to the
// local libraries search paths when switching to the actual edition-based resolution.
List<TruffleFile> preloadedPackagePaths = OptionsHelper.getPreloadedPackagesPaths(environment);
for (var packagePath : preloadedPackagePaths) {
Optional<Package<TruffleFile>> pkgOpt =
ScalaConversions.asJava(packageManager.fromDirectory(packagePath));
if (asPackage.isPresent()) {
Package<TruffleFile> pkg = asPackage.get();
boolean nameExists = packageMap.containsKey(pkg.name());
if (nameExists) {
shadowedPackages.add(
new ShadowedPackage(
packageMap.get(pkg.name()).root().getPath(), pkg.root().getPath(), pkg.name()));
packageMap.remove(pkg.name());
}
packageMap.put(pkg.name(), pkg);
if (pkgOpt.isPresent()) {
var pkg = pkgOpt.get();
packagesToPreload.add(pkg);
} else {
logger.warning("Could not preload a package.");
}
}
packages.addAll(packageMap.values());
// TODO [RW] this is left here temporarily, until the edition system takes over resolving the
// std-lib
if (home != null) {
HomeManager<TruffleFile> homeManager =
new HomeManager<>(environment.getInternalTruffleFile(home), fs);
homeManager.loadStdLib().forEach(packagesToPreload::add);
}
packages.forEach(
pkg -> {
List<TruffleFile> classPathItems =
ScalaConversions.asJava(pkg.listPolyglotExtensions("java"));
classPathItems.forEach(environment::addToHostClassPath);
});
Map<String, Module> knownFiles =
packages.stream()
.flatMap(
p ->
ScalaConversions.asJava(p.listSources()).stream()
.map(srcFile -> new Module(srcFile.qualifiedName(), p, srcFile.file())))
.collect(Collectors.toMap(mod -> mod.getName().toString(), mod -> mod));
topScope = new TopLevelScope(builtins, knownFiles);
packageRepository.registerForPreload(CollectionConverters.asScala(packagesToPreload).toSeq());
}
public TruffleFile getTruffleFile(File file) {
@ -217,7 +211,7 @@ public class Context {
*/
public Optional<QualifiedName> getModuleNameForFile(File path) {
TruffleFile p = getTruffleFile(path);
return packages.stream()
return ScalaConversions.asJava(packageRepository.getLoadedPackages()).stream()
.filter(pkg -> p.startsWith(pkg.sourceDir()))
.map(pkg -> pkg.moduleNameForFile(p))
.findFirst();
@ -231,23 +225,7 @@ public class Context {
* @param newName the new project name
*/
public void renameProject(String namespace, String oldName, String newName) {
renamePackages(namespace, oldName, newName);
topScope.renameProjectInModules(namespace, oldName, newName);
}
private void renamePackages(String namespace, String oldName, String newName) {
List<Package<TruffleFile>> toChange =
packages.stream()
.filter(
p -> p.config().namespace().equals(namespace) && p.config().name().equals(oldName))
.collect(Collectors.toList());
packages.removeAll(toChange);
List<Package<TruffleFile>> renamed =
toChange.stream().map(p -> p.setPackageName(newName)).collect(Collectors.toList());
packages.addAll(renamed);
packageRepository.renameProject(namespace, oldName, newName);
}
/**
@ -294,7 +272,7 @@ public class Context {
if (file == null) {
return Optional.empty();
}
return packages.stream()
return ScalaConversions.asJava(packageRepository.getLoadedPackages()).stream()
.filter(pkg -> file.getAbsoluteFile().startsWith(pkg.root().getAbsoluteFile()))
.findFirst();
}
@ -317,7 +295,7 @@ public class Context {
* @return an object containing the builtin functions
*/
public Builtins getBuiltins() {
return getTopScope().getBuiltins();
return this.builtins;
}
/**
@ -386,16 +364,6 @@ public class Context {
return isCachingDisabled;
}
/** @return the list of shadowed packages */
public List<ShadowedPackage> getShadowedPackages() {
return shadowedPackages;
}
/** @return the pre-loaded packages */
public List<Package<TruffleFile>> getPackages() {
return packages;
}
/** @return the compiler configuration for this language */
public CompilerConfig getCompilerConfig() {
return compilerConfig;

View File

@ -14,41 +14,39 @@ import com.oracle.truffle.api.library.ExportLibrary;
import com.oracle.truffle.api.library.ExportMessage;
import java.io.File;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import org.enso.compiler.PackageRepository;
import org.enso.interpreter.Language;
import org.enso.interpreter.runtime.Context;
import org.enso.interpreter.runtime.Module;
import org.enso.interpreter.runtime.builtin.Builtins;
import org.enso.interpreter.runtime.data.Array;
import org.enso.interpreter.runtime.type.Types;
import org.enso.interpreter.util.ScalaConversions;
import org.enso.pkg.Package;
import org.enso.pkg.QualifiedName;
import org.enso.pkg.QualifiedName$;
import org.enso.polyglot.MethodNames;
/** Represents the top scope of Enso execution, containing all the importable modules. */
@ExportLibrary(InteropLibrary.class)
public class TopLevelScope implements TruffleObject {
private final Builtins builtins;
private final Map<String, Module> modules;
private final PackageRepository packageRepository;
/**
* Creates a new instance of top scope.
*
* @param builtins the automatically-imported builtin module.
* @param modules the initial modules this scope contains.
* @param packageRepository the {@link PackageRepository} instance that manages loaded packages
*/
public TopLevelScope(Builtins builtins, Map<String, Module> modules) {
public TopLevelScope(Builtins builtins, PackageRepository packageRepository) {
this.builtins = builtins;
this.modules = modules;
this.packageRepository = packageRepository;
}
/** @return the list of modules in the scope. */
public Collection<Module> getModules() {
return modules.values();
return ScalaConversions.asJava(packageRepository.getLoadedModules());
}
/**
@ -58,10 +56,7 @@ public class TopLevelScope implements TruffleObject {
* @return empty result if the module does not exist or the requested module.
*/
public Optional<Module> getModule(String name) {
if (name.equals(Builtins.MODULE_NAME)) {
return Optional.of(builtins.getModule());
}
return Optional.ofNullable(modules.get(name));
return ScalaConversions.asJava(packageRepository.getLoadedModule(name));
}
/**
@ -73,32 +68,10 @@ public class TopLevelScope implements TruffleObject {
*/
public Module createModule(QualifiedName name, Package<TruffleFile> pkg, TruffleFile sourceFile) {
Module module = new Module(name, pkg, sourceFile);
modules.put(name.toString(), module);
packageRepository.registerModuleCreatedInRuntime(module);
return module;
}
/**
* Renames a project part of the included modules.
*
* @param oldName the old project name
* @param newName the new project name
*/
public void renameProjectInModules(String namespace, String oldName, String newName) {
String separator = QualifiedName$.MODULE$.separator();
List<String> keys =
modules.keySet().stream()
.filter(name -> name.startsWith(namespace + separator + oldName + separator))
.collect(Collectors.toList());
keys.stream()
.map(modules::remove)
.forEach(
module -> {
module.renameProject(newName);
modules.put(module.getName().toString(), module);
});
}
/**
* Returns the builtins module.
*
@ -145,15 +118,12 @@ public class TopLevelScope implements TruffleObject {
throws ArityException, UnsupportedTypeException, UnknownIdentifierException {
String moduleName = Types.extractArguments(arguments, String.class);
if (moduleName.equals(Builtins.MODULE_NAME)) {
return scope.builtins.getModule();
}
Module module = scope.modules.get(moduleName);
if (module == null) {
var module = scope.getModule(moduleName);
if (module.isEmpty()) {
throw UnknownIdentifierException.create(moduleName);
}
return module;
return module.get();
}
private static Module createModule(TopLevelScope scope, Object[] arguments, Context context)
@ -169,14 +139,14 @@ public class TopLevelScope implements TruffleObject {
QualifiedName qualName = QualifiedName.fromString(args.getFirst());
File location = new File(args.getSecond());
Module module = new Module(qualName, null, context.getTruffleFile(location));
scope.modules.put(qualName.toString(), module);
scope.packageRepository.registerModuleCreatedInRuntime(module);
return module;
}
private static Object unregisterModule(TopLevelScope scope, Object[] arguments, Context context)
throws ArityException, UnsupportedTypeException {
String name = Types.extractArguments(arguments, String.class);
scope.modules.remove(name);
scope.packageRepository.deregisterModule(name);
return context.getNothing().newInstance();
}

View File

@ -4,11 +4,23 @@ import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.TruffleLogger;
import com.oracle.truffle.api.instrumentation.EventBinding;
import com.oracle.truffle.api.instrumentation.ExecutionEventListener;
import com.oracle.truffle.api.interop.*;
import com.oracle.truffle.api.interop.ArityException;
import com.oracle.truffle.api.interop.InteropLibrary;
import com.oracle.truffle.api.interop.UnknownIdentifierException;
import com.oracle.truffle.api.interop.UnsupportedMessageException;
import com.oracle.truffle.api.interop.UnsupportedTypeException;
import com.oracle.truffle.api.source.SourceSection;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import java.util.function.Consumer;
import org.enso.compiler.context.ChangesetBuilder;
import org.enso.interpreter.instrument.Endpoint;
import org.enso.interpreter.instrument.IdExecutionInstrument;
import org.enso.interpreter.instrument.MethodCallsCache;
import org.enso.interpreter.instrument.NotificationHandler;
import org.enso.interpreter.instrument.RuntimeCache;
import org.enso.interpreter.instrument.UpdatesSynchronizationState;
import org.enso.interpreter.instrument.execution.LocationFilter;
@ -21,7 +33,12 @@ import org.enso.interpreter.runtime.callable.function.Function;
import org.enso.interpreter.runtime.error.PanicException;
import org.enso.interpreter.runtime.scope.ModuleScope;
import org.enso.interpreter.runtime.state.data.EmptyMap;
import org.enso.interpreter.service.error.*;
import org.enso.interpreter.service.error.ConstructorNotFoundException;
import org.enso.interpreter.service.error.FailedToApplyEditsException;
import org.enso.interpreter.service.error.MethodNotFoundException;
import org.enso.interpreter.service.error.ModuleNotFoundException;
import org.enso.interpreter.service.error.ModuleNotFoundForFileException;
import org.enso.interpreter.service.error.SourceNotFoundException;
import org.enso.polyglot.LanguageInfo;
import org.enso.polyglot.MethodNames;
import org.enso.text.buffer.Rope;
@ -30,13 +47,6 @@ import org.enso.text.editing.JavaEditorAdapter;
import org.enso.text.editing.TextEditor;
import org.enso.text.editing.model;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import java.util.function.Consumer;
/**
* A service allowing externally-triggered code execution, registered by an instance of the
* language.
@ -44,6 +54,7 @@ import java.util.function.Consumer;
public class ExecutionService {
private final Context context;
private final IdExecutionInstrument idExecutionInstrument;
private final NotificationHandler.Forwarder notificationForwarder;
private final InteropLibrary interopLibrary = InteropLibrary.getFactory().getUncached();
private final TruffleLogger logger = TruffleLogger.getLogger(LanguageInfo.ID);
@ -54,9 +65,13 @@ public class ExecutionService {
* @param idExecutionInstrument an instance of the {@link IdExecutionInstrument} to use in the
* course of executions.
*/
public ExecutionService(Context context, IdExecutionInstrument idExecutionInstrument) {
public ExecutionService(
Context context,
IdExecutionInstrument idExecutionInstrument,
NotificationHandler.Forwarder notificationForwarder) {
this.idExecutionInstrument = idExecutionInstrument;
this.context = context;
this.notificationForwarder = notificationForwarder;
}
/** @return the language context. */
@ -86,6 +101,11 @@ public class ExecutionService {
function, EmptyMap.create(), new Object[] {atomConstructor.newInstance()});
}
public void initializeLanguageServerConnection(Endpoint endpoint) {
var notificationHandler = new NotificationHandler.InteractiveMode(endpoint);
notificationForwarder.addListener(notificationHandler);
}
/**
* Executes a function with given arguments, represented as runtime language-level objects.
*

View File

@ -23,7 +23,6 @@ import org.enso.syntax.text.Parser.IDMap
import org.enso.syntax.text.{AST, Parser}
import java.io.StringReader
import scala.jdk.CollectionConverters._
import scala.jdk.OptionConverters._
/** This class encapsulates the static transformation processes that take place
@ -330,12 +329,6 @@ class Compiler(
def runErrorHandling(
modules: List[Module]
): Unit = {
val shadowed = context.getShadowedPackages.asScala
if (shadowed.nonEmpty) {
context.getOut.println("Modules were shadowed during loading:")
}
shadowed.foreach(s => context.getOut.println(s.toString))
if (context.isStrictErrors) {
val diagnostics = modules.map { module =>
val errors = GatherDiagnostics

View File

@ -1,24 +1,65 @@
package org.enso.compiler
import org.enso.interpreter.runtime.Context
import com.oracle.truffle.api.TruffleFile
import com.typesafe.scalalogging.Logger
import org.enso.distribution.DistributionManager
import org.enso.editions.{LibraryName, LibraryVersion}
import org.enso.interpreter.instrument.NotificationHandler
import org.enso.interpreter.runtime.builtin.Builtins
import org.enso.interpreter.runtime.util.TruffleFileSystem
import org.enso.interpreter.runtime.{Context, Module}
import org.enso.librarymanager.{ResolvedLibrary, ResolvingLibraryProvider}
import org.enso.librarymanager.local.DefaultLocalLibraryProvider
import org.enso.logger.masking.MaskedPath
import org.enso.pkg.{Package, PackageManager, QualifiedName}
import scala.jdk.CollectionConverters._
import java.nio.file.Path
import scala.util.Try
/** Manages loaded packages and modules. */
trait PackageRepository {
/** Informs the repository that it should populate the top scope with modules
* belonging to a given package.
*
* @param namespace the namespace of the package.
* @param name the package name.
* @param libraryName the name of the library that should be loaded
* @return `Right(())` if the package was already loaded or successfully
* downloaded. A `Left` containing an error otherwise.
*/
def ensurePackageIsLoaded(
namespace: String,
name: String
libraryName: LibraryName
): Either[PackageRepository.Error, Unit]
/** Get a sequence of currently loaded packages. */
def getLoadedPackages(): Seq[Package[TruffleFile]]
/** Get a sequence of currently loaded modules. */
def getLoadedModules(): Seq[Module]
/** Get a loaded module by its qualified name. */
def getLoadedModule(qualifiedName: String): Option[Module]
/** Register the main project package. */
def registerMainProjectPackage(
libraryName: LibraryName,
pkg: Package[TruffleFile]
): Unit
/** Register a single module, outside of any packages or part of an already
* loaded package, that has been created manually during runtime.
*/
def registerModuleCreatedInRuntime(module: Module): Unit
/** Removes a module with the given name from the list of loaded modules. */
def deregisterModule(qualifiedName: String): Unit
/** Modifies package and module names to reflect the project name change. */
def renameProject(namespace: String, oldName: String, newName: String): Unit
/** This is a temporary workaround that should be removed once we get
* integrated with the editions.
*/
def registerForPreload(packages: Seq[Package[TruffleFile]]): Unit
}
object PackageRepository {
@ -28,28 +69,298 @@ object PackageRepository {
object Error {
/** An error reported when the requested package does not exist.
/** Indicates that a resolution error has happened, for example the package
* was not defined in the selected edition.
*/
case object PackageDoesNotExist extends Error
case class PackageCouldNotBeResolved(cause: Throwable) extends Error {
override def toString: String =
s"The package could not be resolved: ${cause.getMessage}"
}
/** Indicates that the package was missing and a download was attempted, but
* it failed - for example due to connectivity problems or just because the
* package did not exist in the repository.
*/
case class PackageDownloadFailed(cause: Throwable) extends Error {
override def toString: String =
s"The package download has failed: ${cause.getMessage}"
}
/** Indicates that the package was already present in the cache (or within
* local packages), but it could not be loaded, possibly to a filesystem
* error or insufficient permissions.
*/
case class PackageLoadingError(cause: String) extends Error {
override def toString: String =
s"The package could not be loaded: $cause"
}
}
/** A temporary package repository, only able to resolve packages known
* upfront to the language context.
/** The default [[PackageRepository]] implementation.
*
* @param libraryProvider the [[ResolvingLibraryProvider]] which resolves
* which library version should be imported and
* locates them (or downloads if they are missing)
* @param context the language context
* @param builtins the builtins module
* @param notificationHandler a notification handler
*/
class Default(context: Context) extends PackageRepository {
class Default(
libraryProvider: ResolvingLibraryProvider,
context: Context,
builtins: Builtins,
notificationHandler: NotificationHandler
) extends PackageRepository {
private val logger = Logger[Default]
implicit private val fs: TruffleFileSystem = new TruffleFileSystem
private val packageManager = new PackageManager[TruffleFile]
/** The mapping containing all loaded packages.
*
* It should be modified only from within synchronized sections, but it may
* be always read. Thus elements should be added to this mapping only after
* all library loading bookkeeping has been finished - so that if other,
* unsynchronized threads read this map, every element it contains is
* already fully processed.
*/
val loadedPackages
: collection.concurrent.Map[LibraryName, Option[Package[TruffleFile]]] = {
val builtinsName = LibraryName(Builtins.NAMESPACE, Builtins.PACKAGE_NAME)
collection.concurrent.TrieMap(builtinsName -> None)
}
/** The mapping containing loaded modules. */
val loadedModules: collection.concurrent.Map[String, Module] =
collection.concurrent.TrieMap(Builtins.MODULE_NAME -> builtins.getModule)
/** @inheritdoc */
override def registerMainProjectPackage(
libraryName: LibraryName,
pkg: Package[TruffleFile]
): Unit = registerPackageInternal(
libraryName = libraryName,
pkg = pkg,
libraryVersion = LibraryVersion.Local,
isLibrary = false
)
private def registerPackageInternal(
libraryName: LibraryName,
libraryVersion: LibraryVersion,
pkg: Package[TruffleFile],
isLibrary: Boolean
): Unit = {
val extensions = pkg.listPolyglotExtensions("java")
extensions.foreach(context.getEnvironment.addToHostClassPath)
pkg.listSources
.map { srcFile =>
new Module(srcFile.qualifiedName, pkg, srcFile.file)
}
.foreach(registerModule)
if (isLibrary) {
val root = Path.of(pkg.root.toString)
notificationHandler.addedLibrary(libraryName, libraryVersion, root)
}
loadedPackages.put(libraryName, Some(pkg))
}
/** This package modifies the [[loadedPackages]], so it should be only
* called from within synchronized sections.
*/
private def loadPackage(
libraryName: LibraryName,
libraryVersion: LibraryVersion,
root: Path
): Either[Error, Unit] = Try {
logger.debug(
s"Loading library $libraryName from " +
s"[${MaskedPath(root).applyMasking()}]."
)
val rootFile = context.getEnvironment.getInternalTruffleFile(
root.toAbsolutePath.normalize.toString
)
val pkg = packageManager.loadPackage(rootFile).get
registerPackageInternal(
libraryName = libraryName,
libraryVersion = libraryVersion,
pkg = pkg,
isLibrary = true
)
}.toEither.left.map { error => Error.PackageLoadingError(error.getMessage) }
/** @inheritdoc */
override def ensurePackageIsLoaded(
libraryName: LibraryName
): Either[Error, Unit] = {
runPreload()
if (loadedPackages.contains(libraryName)) Right(())
else {
logger.trace(s"Resolving library $libraryName.")
val resolvedLibrary = libraryProvider.findLibrary(libraryName)
this.synchronized {
// We check again inside of the monitor, in case that some other
// thread has just added this library.
if (loadedPackages.contains(libraryName)) Right(())
else
resolvedLibrary
.flatMap { library =>
loadPackage(library.name, library.version, library.location)
}
.left
.map {
case ResolvingLibraryProvider.Error.NotResolved(details) =>
Error.PackageCouldNotBeResolved(details)
case ResolvingLibraryProvider.Error.DownloadFailed(reason) =>
Error.PackageDownloadFailed(reason)
case ResolvingLibraryProvider.Error.RequestedLocalLibraryDoesNotExist =>
Error.PackageLoadingError(
"The local library has not been found on the local " +
"libraries search paths."
)
}
}
}
}
/** @inheritdoc */
override def getLoadedModules(): Seq[Module] = {
runPreload()
loadedModules.values.toSeq
}
/** @inheritdoc */
override def getLoadedPackages(): Seq[Package[TruffleFile]] = {
runPreload()
loadedPackages.values.toSeq.flatten
}
/** @inheritdoc */
override def getLoadedModule(qualifiedName: String): Option[Module] = {
runPreload()
loadedModules.get(qualifiedName)
}
/** @inheritdoc */
override def registerModuleCreatedInRuntime(module: Module): Unit =
registerModule(module)
private def registerModule(module: Module): Unit =
loadedModules.put(module.getName.toString, module)
override def deregisterModule(qualifiedName: String): Unit =
loadedModules.remove(qualifiedName)
/** @inheritdoc */
override def renameProject(
namespace: String,
name: String
): Either[PackageRepository.Error, Unit] =
if (
(name == Builtins.PACKAGE_NAME && namespace == Builtins.NAMESPACE) ||
context.getPackages.asScala
.exists(p => p.name == name && p.namespace == namespace)
) Right(())
else Left(Error.PackageDoesNotExist)
oldName: String,
newName: String
): Unit = this.synchronized {
renamePackages(namespace, oldName, newName)
renameModules(namespace, oldName, newName)
}
private def renamePackages(
namespace: String,
oldName: String,
newName: String
): Unit = {
val toChange = loadedPackages.toSeq.filter { case (name, _) =>
name.namespace == namespace && name.name == oldName
}
for ((key, _) <- toChange) {
loadedPackages.remove(key)
}
for ((key, pkgOption) <- toChange) {
val newPkg = pkgOption.map(_.setPackageName(newName))
val newKey = key.copy(name = newName)
loadedPackages.put(newKey, newPkg)
}
}
private def renameModules(
namespace: String,
oldName: String,
newName: String
): Unit = {
val separator: String = QualifiedName.separator
val keys = loadedModules.keySet.filter(name =>
name.startsWith(namespace + separator + oldName + separator)
)
for {
key <- keys
module <- loadedModules.remove(key)
} {
module.renameProject(newName)
loadedModules.put(module.getName.toString, module)
}
}
/** Temporary workaround, will be removed once editions are integrated. */
private var toPreload: List[Package[TruffleFile]] = Nil
private def runPreload(): Unit = {
for (pkg <- toPreload) {
registerPackageInternal(
libraryName = pkg.libraryName,
pkg = pkg,
libraryVersion = LibraryVersion.Local,
isLibrary = true
)
}
toPreload = Nil
}
/** @inheritdoc */
override def registerForPreload(packages: Seq[Package[TruffleFile]]): Unit =
toPreload ++= packages
}
/** A temporary [[ResolvingLibraryProvider]] that ignores the edition and just
* provides the local libraries.
*
* TODO [RW] it should be removed once the editions are integrated
*/
private class TemporaryLocalProvider(searchPaths: List[Path])
extends ResolvingLibraryProvider {
private val localRepo = new DefaultLocalLibraryProvider(searchPaths)
override def findLibrary(
name: LibraryName
): Either[ResolvingLibraryProvider.Error, ResolvedLibrary] =
localRepo
.findLibrary(name)
.map(ResolvedLibrary(name, LibraryVersion.Local, _))
.toRight {
ResolvingLibraryProvider.Error.RequestedLocalLibraryDoesNotExist
}
}
/** A temporary helper constructor for [[PackageRepository]] that does not
* need any edition configuration.
*
* TODO [RW] it should be removed once the editions are integrated
*/
def makeLegacyRepository(
distributionManager: DistributionManager,
context: Context,
builtins: Builtins,
notificationHandler: NotificationHandler
): PackageRepository = {
val searchPaths = distributionManager.paths.localLibrariesSearchPaths.toList
new Default(
new TemporaryLocalProvider(searchPaths),
context,
builtins,
notificationHandler
)
}
}

View File

@ -6631,12 +6631,15 @@ object IR {
" but the module does not belong to a project."
}
/** Used when an import statement triggers loading of a package that does not exist.
/** Used when an import statement triggers loading of a package that could
* not be loaded.
*
* @param name the module name.
*/
case class PackageDoesNotExist(name: String) extends Reason {
case class PackageCouldNotBeLoaded(name: String, reason: String)
extends Reason {
override def message: String = s"Package containing the module $name" +
s" could not be found."
s" could not be loaded: $reason"
}
/** Used when an import statement refers to a module that does not exist.

View File

@ -3,7 +3,9 @@ package org.enso.compiler.phase
import org.enso.compiler.Compiler
import org.enso.compiler.core.IR
import org.enso.compiler.data.BindingsMap
import org.enso.compiler.exception.CompilerError
import org.enso.compiler.pass.analyse.BindingAnalysis
import org.enso.editions.LibraryName
import org.enso.interpreter.runtime.Module
import scala.collection.mutable
@ -60,38 +62,44 @@ class ImportResolver(compiler: Compiler) {
val exp = ir.exports
.collect { case ex: IR.Module.Scope.Export.Module => ex }
.find(_.name.name == impName)
val isLoaded = imp.name.parts match {
val libraryName = imp.name.parts match {
case namespace :: name :: _ =>
compiler.packageRepository.ensurePackageIsLoaded(
namespace.name,
name.name
).isRight
case _ => false
LibraryName(namespace.name, name.name)
case _ =>
throw new CompilerError(
"Imports should containt at least two segments after " +
"desugaring."
)
}
if (isLoaded) {
compiler.getModule(impName) match {
case Some(module) =>
(
imp,
Some(BindingsMap.ResolvedImport(imp, exp, module))
)
case None =>
(
IR.Error.ImportExport(
compiler.packageRepository
.ensurePackageIsLoaded(libraryName) match {
case Right(()) =>
compiler.getModule(impName) match {
case Some(module) =>
(
imp,
IR.Error.ImportExport.ModuleDoesNotExist(impName)
),
None
)
}
} else {
(
IR.Error.ImportExport(
imp,
IR.Error.ImportExport.PackageDoesNotExist(impName)
),
None
)
Some(BindingsMap.ResolvedImport(imp, exp, module))
)
case None =>
(
IR.Error.ImportExport(
imp,
IR.Error.ImportExport.ModuleDoesNotExist(impName)
),
None
)
}
case Left(loadingError) =>
(
IR.Error.ImportExport(
imp,
IR.Error.ImportExport.PackageCouldNotBeLoaded(
impName,
loadingError.toString
)
),
None
)
}
case other => (other, None)
}

View File

@ -1,7 +1,5 @@
package org.enso.interpreter.instrument
import java.nio.ByteBuffer
import com.oracle.truffle.api.TruffleContext
import org.enso.interpreter.instrument.command.CommandFactory
import org.enso.interpreter.instrument.execution.{
@ -12,6 +10,8 @@ import org.enso.interpreter.service.ExecutionService
import org.enso.polyglot.runtime.Runtime.Api
import org.graalvm.polyglot.io.MessageEndpoint
import java.nio.ByteBuffer
/** A message endpoint implementation used by the
* [[org.enso.interpreter.instrument.RuntimeServerInstrument]].
*/
@ -75,6 +75,7 @@ final class Handler {
truffleContext
)
commandProcessor = new CommandExecutionEngine(interpreterCtx)
executionService.initializeLanguageServerConnection(endpoint)
endpoint.sendToClient(Api.Response(Api.InitializedNotification()))
}

View File

@ -0,0 +1,111 @@
package org.enso.interpreter.instrument
import com.typesafe.scalalogging.Logger
import org.enso.cli.ProgressBar
import org.enso.cli.task.{ProgressReporter, TaskProgress}
import org.enso.editions.{LibraryName, LibraryVersion}
import org.enso.polyglot.runtime.Runtime.{Api, ApiResponse}
import java.nio.file.Path
/** A class that forwards notifications about loaded libraries and long-running
* tasks to the user interface.
*/
trait NotificationHandler extends ProgressReporter {
/** Called when a library has been loaded.
*
* @param libraryName name of the added library
* @param libraryVersion selected version
* @param location path to the location from which the library is loaded
*/
def addedLibrary(
libraryName: LibraryName,
libraryVersion: LibraryVersion,
location: Path
): Unit
}
object NotificationHandler {
/** A [[NotificationHandler]] for text mode.
*
* It ignores library notifications and displays progress as a progress bar
* in the CLI, as long as the stdout is connected to a terminal.
*/
object TextMode extends NotificationHandler {
/** @inheritdoc */
override def addedLibrary(
libraryName: LibraryName,
libraryVersion: LibraryVersion,
location: Path
): Unit = {
// Library notifications are deliberately ignored in text mode.
}
/** @inheritdoc */
override def trackProgress(message: String, task: TaskProgress[_]): Unit = {
Logger[TextMode.type].info(message)
if (System.console() != null) {
ProgressBar.waitWithProgress(task)
}
}
}
/** A [[NotificationHandler]] that forwards messages to other
* NotificationHandlers.
*/
class Forwarder extends NotificationHandler {
private var listeners: List[NotificationHandler] = Nil
/** @inheritdoc */
override def addedLibrary(
libraryName: LibraryName,
libraryVersion: LibraryVersion,
location: Path
): Unit = for (listener <- listeners)
listener.addedLibrary(libraryName, libraryVersion, location)
/** @inheritdoc */
override def trackProgress(message: String, task: TaskProgress[_]): Unit =
for (listener <- listeners) listener.trackProgress(message, task)
/** Registers a new listener. */
def addListener(listener: NotificationHandler): Unit =
listeners ::= listener
}
/** A [[NotificationHandler]] for interactive mode, which forwards the
* notifications to the Language Server, which then should forward them to
* the IDE.
*/
class InteractiveMode(endpoint: Endpoint) extends NotificationHandler {
private val logger = Logger[InteractiveMode]
private def sendMessage(message: ApiResponse): Unit = {
val response = Api.Response(None, message)
endpoint.sendToClient(response)
}
/** @inheritdoc */
override def addedLibrary(
libraryName: LibraryName,
libraryVersion: LibraryVersion,
location: Path
): Unit = sendMessage(
Api.LibraryLoaded(
namespace = libraryName.namespace,
name = libraryName.name,
version = libraryVersion.toString,
location = location.toFile
)
)
/** @inheritdoc */
override def trackProgress(message: String, task: TaskProgress[_]): Unit = {
logger.info(message)
// TODO [RW] this should be implemented once progress tracking is used by downloads
}
}
}

View File

@ -0,0 +1,10 @@
package org.enso.interpreter.runtime
import org.enso.distribution.{DistributionManager, Environment}
/** A [[DistributionManager]] for the runtime.
*
* It is used to resolve paths to library cache etc.
*/
object RuntimeDistributionManager
extends DistributionManager(new Environment {})

View File

@ -21,7 +21,7 @@ trait PackageTest extends AnyFlatSpec with Matchers with ValueEquality {
.newBuilder(LanguageInfo.ID)
.allowExperimentalOptions(true)
.allowAllAccess(true)
.option(RuntimeOptions.PACKAGES_PATH, pkgPath.getAbsolutePath)
.option(RuntimeOptions.PROJECT_ROOT, pkgPath.getAbsolutePath)
.option(RuntimeOptions.STRICT_ERRORS, "true")
.out(output)
.in(System.in)

View File

@ -56,12 +56,13 @@ class RuntimeErrorsTest
.newBuilder(LanguageInfo.ID)
.allowExperimentalOptions(true)
.allowAllAccess(true)
.option(RuntimeOptions.PACKAGES_PATH, pkg.root.getAbsolutePath)
.option(RuntimeOptions.PROJECT_ROOT, pkg.root.getAbsolutePath)
.option(RuntimeOptions.LOG_LEVEL, "WARNING")
.option(RuntimeOptions.INTERPRETER_SEQUENTIAL_COMMAND_EXECUTION, "true")
.option(RuntimeOptions.ENABLE_PROJECT_SUGGESTIONS, "false")
.option(RuntimeOptions.ENABLE_GLOBAL_SUGGESTIONS, "false")
.option(RuntimeServerInfo.ENABLE_OPTION, "true")
.option(RuntimeOptions.INTERACTIVE_MODE, "true")
.out(out)
.serverTransport { (uri, peer) =>
if (uri.toString == RuntimeServerInfo.URI) {

View File

@ -50,12 +50,13 @@ class RuntimeInstrumentTest
.newBuilder(LanguageInfo.ID)
.allowExperimentalOptions(true)
.allowAllAccess(true)
.option(RuntimeOptions.PACKAGES_PATH, pkg.root.getAbsolutePath)
.option(RuntimeOptions.PROJECT_ROOT, pkg.root.getAbsolutePath)
.option(RuntimeOptions.LOG_LEVEL, "WARNING")
.option(RuntimeOptions.INTERPRETER_SEQUENTIAL_COMMAND_EXECUTION, "true")
.option(RuntimeOptions.ENABLE_PROJECT_SUGGESTIONS, "false")
.option(RuntimeOptions.ENABLE_GLOBAL_SUGGESTIONS, "false")
.option(RuntimeServerInfo.ENABLE_OPTION, "true")
.option(RuntimeOptions.INTERACTIVE_MODE, "true")
.out(out)
.serverTransport { (uri, peer) =>
if (uri.toString == RuntimeServerInfo.URI) {

View File

@ -54,12 +54,13 @@ class RuntimeServerTest
.newBuilder(LanguageInfo.ID)
.allowExperimentalOptions(true)
.allowAllAccess(true)
.option(RuntimeOptions.PACKAGES_PATH, pkg.root.getAbsolutePath)
.option(RuntimeOptions.PROJECT_ROOT, pkg.root.getAbsolutePath)
.option(RuntimeOptions.LOG_LEVEL, "WARNING")
.option(RuntimeOptions.INTERPRETER_SEQUENTIAL_COMMAND_EXECUTION, "true")
.option(RuntimeOptions.ENABLE_PROJECT_SUGGESTIONS, "false")
.option(RuntimeOptions.ENABLE_GLOBAL_SUGGESTIONS, "false")
.option(RuntimeServerInfo.ENABLE_OPTION, "true")
.option(RuntimeOptions.INTERACTIVE_MODE, "true")
.logHandler(logOut)
.out(out)
.serverTransport { (uri, peer) =>

View File

@ -1,11 +1,5 @@
package org.enso.interpreter.test.instrument
import java.io.{ByteArrayOutputStream, File}
import java.nio.ByteBuffer
import java.nio.file.{Files, Paths}
import java.util.UUID
import java.util.concurrent.{LinkedBlockingQueue, TimeUnit}
import org.enso.interpreter.test.Metadata
import org.enso.pkg.{Package, PackageManager}
import org.enso.polyglot._
@ -13,19 +7,25 @@ import org.enso.polyglot.runtime.Runtime.Api
import org.enso.testkit.OsSpec
import org.graalvm.polyglot.Context
import org.graalvm.polyglot.io.MessageEndpoint
import org.scalatest.BeforeAndAfterEach
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers
import org.scalatest.{BeforeAndAfterAll, BeforeAndAfterEach}
import java.io.{ByteArrayOutputStream, File}
import java.nio.ByteBuffer
import java.nio.file.{Files, Paths}
import java.util.UUID
import java.util.concurrent.{LinkedBlockingQueue, TimeUnit}
@scala.annotation.nowarn("msg=multiarg infix syntax")
class RuntimeStdlibTest
extends AnyFlatSpec
with Matchers
with BeforeAndAfterEach
with BeforeAndAfterAll
with OsSpec {
final val ContextPathSeparator: String =
if (isWindows) ";" else ":"
final val ContextPathSeparator: String = File.pathSeparator
var context: TestContext = _
@ -47,13 +47,12 @@ class RuntimeStdlibTest
.newBuilder(LanguageInfo.ID)
.allowExperimentalOptions(true)
.allowAllAccess(true)
.option(
RuntimeOptions.PACKAGES_PATH,
toPackagesPath(pkg.root.getAbsolutePath, stdlib.toString)
)
.option(RuntimeOptions.PROJECT_ROOT, pkg.root.getAbsolutePath)
.option(RuntimeOptions.PRELOADED_PACKAGES_PATHS, stdlib.toString)
.option(RuntimeOptions.LOG_LEVEL, "WARNING")
.option(RuntimeOptions.INTERPRETER_SEQUENTIAL_COMMAND_EXECUTION, "true")
.option(RuntimeServerInfo.ENABLE_OPTION, "true")
.option(RuntimeOptions.INTERACTIVE_MODE, "true")
.out(out)
.serverTransport { (uri, peer) =>
if (uri.toString == RuntimeServerInfo.URI) {
@ -77,7 +76,7 @@ class RuntimeStdlibTest
executionContext.context.initialize(LanguageInfo.ID)
def toPackagesPath(paths: String*): String =
paths.mkString(ContextPathSeparator)
paths.mkString(File.pathSeparator)
def writeMain(contents: String): File =
Files.write(pkg.mainFile.toPath, contents.getBytes).toFile
@ -92,7 +91,7 @@ class RuntimeStdlibTest
def send(msg: Api.Request): Unit = endPoint.sendBinary(Api.serialize(msg))
def receiveNone: Option[Api.Response] = {
def receiveOne: Option[Api.Response] = {
Option(messageQueue.poll())
}
@ -165,7 +164,7 @@ class RuntimeStdlibTest
context.send(
Api.Request(Api.OpenFileNotification(mainFile, contents, true))
)
context.receiveNone shouldEqual None
context.receiveOne shouldEqual None
// push main
context.send(
@ -181,16 +180,17 @@ class RuntimeStdlibTest
)
)
)
val response =
val responses =
context.receiveAllUntil(
context.executionComplete(contextId),
timeout = 60
)
response should contain allOf (
responses should contain allOf (
Api.Response(requestId, Api.PushContextResponse(contextId)),
context.executionComplete(contextId)
)
val suggestions = response.collect {
val suggestions = responses.collect {
case Api.Response(
None,
Api.SuggestionsDatabaseModuleUpdateNotification(_, _, as, xs)
@ -199,7 +199,7 @@ class RuntimeStdlibTest
}
suggestions.isEmpty shouldBe false
val builtinsSuggestions = response.collect {
val builtinsSuggestions = responses.collect {
case Api.Response(
None,
Api.SuggestionsDatabaseModuleUpdateNotification(file, _, as, xs)
@ -208,6 +208,16 @@ class RuntimeStdlibTest
}
builtinsSuggestions.length shouldBe 1
val contentRootNotifications = responses.collect {
case Api.Response(
None,
Api.LibraryLoaded(namespace, name, version, _)
) =>
(namespace, name, version)
}
contentRootNotifications should contain(("Standard", "Base", "local"))
context.consumeOut shouldEqual List("Hello World!")
}

View File

@ -43,11 +43,12 @@ class RuntimeSuggestionUpdatesTest
.newBuilder(LanguageInfo.ID)
.allowExperimentalOptions(true)
.allowAllAccess(true)
.option(RuntimeOptions.PACKAGES_PATH, pkg.root.getAbsolutePath)
.option(RuntimeOptions.PROJECT_ROOT, pkg.root.getAbsolutePath)
.option(RuntimeOptions.LOG_LEVEL, "WARNING")
.option(RuntimeOptions.INTERPRETER_SEQUENTIAL_COMMAND_EXECUTION, "true")
.option(RuntimeOptions.ENABLE_GLOBAL_SUGGESTIONS, "false")
.option(RuntimeServerInfo.ENABLE_OPTION, "true")
.option(RuntimeOptions.INTERACTIVE_MODE, "true")
.out(out)
.serverTransport { (uri, peer) =>
if (uri.toString == RuntimeServerInfo.URI) {
@ -184,7 +185,13 @@ class RuntimeSuggestionUpdatesTest
"main",
List(
Suggestion
.Argument("this", "Enso_Test.Test.Main", false, false, None)
.Argument(
"this",
"Enso_Test.Test.Main",
false,
false,
None
)
),
"Enso_Test.Test.Main",
Constants.ANY,
@ -240,7 +247,13 @@ class RuntimeSuggestionUpdatesTest
"main",
List(
Suggestion
.Argument("this", "Enso_Test.Test.Main", false, false, None)
.Argument(
"this",
"Enso_Test.Test.Main",
false,
false,
None
)
),
"Enso_Test.Test.Main",
Constants.ANY,
@ -318,7 +331,13 @@ class RuntimeSuggestionUpdatesTest
"main",
List(
Suggestion
.Argument("this", "Enso_Test.Test.Main", false, false, None)
.Argument(
"this",
"Enso_Test.Test.Main",
false,
false,
None
)
),
"Enso_Test.Test.Main",
Constants.ANY,
@ -416,7 +435,13 @@ class RuntimeSuggestionUpdatesTest
"main",
List(
Suggestion
.Argument("this", "Enso_Test.Test.Main", false, false, None)
.Argument(
"this",
"Enso_Test.Test.Main",
false,
false,
None
)
),
"Enso_Test.Test.Main",
Constants.ANY,
@ -524,7 +549,13 @@ class RuntimeSuggestionUpdatesTest
"main",
List(
Suggestion
.Argument("this", "Enso_Test.Test.Main", false, false, None)
.Argument(
"this",
"Enso_Test.Test.Main",
false,
false,
None
)
),
"Enso_Test.Test.Main",
Constants.ANY,
@ -590,7 +621,13 @@ class RuntimeSuggestionUpdatesTest
"foo",
List(
Suggestion
.Argument("this", "Enso_Test.Test.Main", false, false, None),
.Argument(
"this",
"Enso_Test.Test.Main",
false,
false,
None
),
Suggestion
.Argument("x", Constants.ANY, false, false, None)
),
@ -652,7 +689,13 @@ class RuntimeSuggestionUpdatesTest
"foo",
List(
Suggestion
.Argument("this", "Enso_Test.Test.Main", false, false, None),
.Argument(
"this",
"Enso_Test.Test.Main",
false,
false,
None
),
Suggestion
.Argument("x", Constants.ANY, false, false, None)
),
@ -768,7 +811,13 @@ class RuntimeSuggestionUpdatesTest
"main",
Seq(
Suggestion
.Argument("this", "Enso_Test.Test.Main", false, false, None)
.Argument(
"this",
"Enso_Test.Test.Main",
false,
false,
None
)
),
"Enso_Test.Test.Main",
Constants.ANY,

View File

@ -53,12 +53,13 @@ class RuntimeVisualisationsTest
.newBuilder(LanguageInfo.ID)
.allowExperimentalOptions(true)
.allowAllAccess(true)
.option(RuntimeOptions.PACKAGES_PATH, pkg.root.getAbsolutePath)
.option(RuntimeOptions.PROJECT_ROOT, pkg.root.getAbsolutePath)
.option(RuntimeOptions.LOG_LEVEL, "WARNING")
.option(RuntimeOptions.INTERPRETER_SEQUENTIAL_COMMAND_EXECUTION, "true")
.option(RuntimeOptions.ENABLE_PROJECT_SUGGESTIONS, "false")
.option(RuntimeOptions.ENABLE_GLOBAL_SUGGESTIONS, "false")
.option(RuntimeServerInfo.ENABLE_OPTION, "true")
.option(RuntimeOptions.INTERACTIVE_MODE, "true")
.logHandler(logOut)
.out(out)
.serverTransport { (uri, peer) =>
@ -180,7 +181,13 @@ class RuntimeVisualisationsTest
Api.ExpressionUpdate(
Main.idMainY,
Some(Constants.INTEGER),
Some(Api.MethodPointer("Enso_Test.Test.Main", Constants.NUMBER, "foo")),
Some(
Api.MethodPointer(
"Enso_Test.Test.Main",
Constants.NUMBER,
"foo"
)
),
Vector(Api.ProfilingInfo.ExecutionTime(0)),
fromCache,
Api.ExpressionUpdate.Payload.Value()

View File

@ -23,8 +23,9 @@ class ImportsTest extends PackageTest {
) should have message "Compilation aborted due to errors."
val outLines = consumeOut
outLines(2) should include(
"Package containing the module Surely_This.Does_Not_Exist.My_Module could" +
" not be found."
"Package containing the module Surely_This.Does_Not_Exist.My_Module " +
"could not be loaded: The package could not be loaded: The local " +
"library has not been found on the local libraries search paths."
)
outLines(3) should include(
"The module Enso_Test.Test_Bad_Imports.Oopsie does not exist."

View File

@ -2,7 +2,9 @@ package org.enso.distribution
import com.typesafe.scalalogging.Logger
import org.enso.distribution.FileSystem.PathSyntax
import org.enso.logger.masking.{MaskedPath, ToLogString}
import java.io.File
import java.nio.file.{Files, Path}
import scala.util.Try
import scala.util.control.NonFatal
@ -19,6 +21,7 @@ import scala.util.control.NonFatal
* @param bundle optional bundle description, containing secondary engine and
* runtime directories
* @param config location of configuration
* @param runRoot root for directories that store runtime files, like lockfiles
* @param locks a directory for storing lockfiles that are used to synchronize
* access to the various components
* @param logs a directory for storing logs
@ -36,6 +39,7 @@ case class DistributionPaths(
engines: Path,
bundle: Option[Bundle],
config: Path,
runRoot: Path,
locks: Path,
logs: Path,
unsafeTemporaryDirectory: Path,
@ -47,16 +51,23 @@ case class DistributionPaths(
/** @inheritdoc */
override def toString: String =
s"""DistributionPaths(
| dataRoot = $dataRoot,
| runtimes = $runtimes,
| engines = $engines,
| bundle = $bundle,
| config = $config,
| locks = $locks,
| tmp = $unsafeTemporaryDirectory,
| ensoHome = $ensoHome
| dataRoot = ${mask(dataRoot)},
| runtimes = ${mask(runtimes)},
| engines = ${mask(engines)},
| bundle = ${bundle.map(_.applyMasking())},
| config = ${mask(config)},
| locks = ${mask(locks)},
| logs = ${mask(logs)},
| tmp = ${mask(unsafeTemporaryDirectory)},
| ensoHome = ${mask(ensoHome)},
| customEditions = ${mask(customEditions)},
| localLibrariesSearchpaths = ${mask(localLibrariesSearchPaths)}
|)""".stripMargin
private def mask(path: Path): String = MaskedPath(path).applyMasking()
private def mask(paths: Seq[Path]): String =
paths.map(p => MaskedPath(p).applyMasking()).toString()
/** Sequence of paths to search for engine installations, in order of
* precedence.
*/
@ -91,7 +102,11 @@ case class DistributionPaths(
* For portable distributions, bundled packages are already included in the
* primary directory.
*/
case class Bundle(engines: Path, runtimes: Path)
case class Bundle(engines: Path, runtimes: Path) extends ToLogString {
override def toLogString(shouldMask: Boolean): String =
s"Bundle(engines = ${MaskedPath(engines).toLogString(shouldMask)}, " +
s"runtimes = ${MaskedPath(runtimes).toLogString(shouldMask)})"
}
/** A helper class that encapsulates management of paths to components of the
* distribution.
@ -123,6 +138,7 @@ class DistributionManager(val env: Environment) {
engines = dataRoot / ENGINES_DIRECTORY,
bundle = detectBundle(),
config = configRoot,
runRoot = runRoot,
locks = runRoot / LOCK_DIRECTORY,
logs = LocallyInstalledDirectories.logDirectory,
unsafeTemporaryDirectory = dataRoot / TMP_DIRECTORY,
@ -132,9 +148,44 @@ class DistributionManager(val env: Environment) {
)
}
/** Returns a mapping of environment variables, such that if it is set to
* another [[DistributionManager]] instance (but one which is not
* [[PortableDistributionManager]], because the portable mark overrides the
* environment variable settings), it will lead to the same paths.
*/
def getEnvironmentToInheritSettings: Map[String, String] = {
def canonize(path: Path): String = path.toAbsolutePath.normalize.toString
def canonizeSeq(paths: Seq[Path]): String =
paths.map(canonize).mkString(File.pathSeparator)
Map(
ENSO_DATA_DIRECTORY -> canonize(paths.dataRoot),
ENSO_CONFIG_DIRECTORY -> canonize(paths.config),
ENSO_RUNTIME_DIRECTORY -> canonize(paths.runRoot),
ENSO_LOG_DIRECTORY -> canonize(paths.logs),
ENSO_HOME -> canonize(paths.ensoHome),
ENSO_EDITION_PATH -> canonizeSeq(paths.customEditions),
ENSO_LIBRARY_PATH -> canonizeSeq(paths.localLibrariesSearchPaths)
)
}
private val ENSO_HOME = "ENSO_HOME"
private val ENSO_EDITION_PATH = "ENSO_EDITION_PATH"
private val ENSO_LIBRARY_PATH = "ENSO_LIBRARY_PATH"
val ENSO_DATA_DIRECTORY = "ENSO_DATA_DIRECTORY"
val ENSO_CONFIG_DIRECTORY = "ENSO_CONFIG_DIRECTORY"
val ENSO_BIN_DIRECTORY = "ENSO_BIN_DIRECTORY"
val ENSO_RUNTIME_DIRECTORY = "ENSO_RUNTIME_DIRECTORY"
val ENSO_LOG_DIRECTORY = "ENSO_LOG_DIRECTORY"
private val ENSO_AUXILIARY_LIBRARY_CACHES = "ENSO_AUXILIARY_LIBRARY_CACHES"
/** List of paths of additional caches for published libraries.
*
* These locations can be used to preload published libraries, for example
* from a shared network drive, so that they do not need to be downloaded.
*/
def auxiliaryLibraryCaches(): Seq[Path] =
env.getEnvPaths(ENSO_AUXILIARY_LIBRARY_CACHES).getOrElse(Seq())
/** Finds the path to the ENSO_HOME directory that is used for keeping user's
* projects, libraries and other custom artifacts.
@ -208,12 +259,6 @@ class DistributionManager(val env: Environment) {
* to determine destination for installed files.
*/
object LocallyInstalledDirectories {
val ENSO_DATA_DIRECTORY = "ENSO_DATA_DIRECTORY"
val ENSO_CONFIG_DIRECTORY = "ENSO_CONFIG_DIRECTORY"
val ENSO_BIN_DIRECTORY = "ENSO_BIN_DIRECTORY"
val ENSO_RUNTIME_DIRECTORY = "ENSO_RUNTIME_DIRECTORY"
val ENSO_LOG_DIRECTORY = "ENSO_LOG_DIRECTORY"
private val XDG_DATA_DIRECTORY = "XDG_DATA_HOME"
private val XDG_CONFIG_DIRECTORY = "XDG_CONFIG_HOME"
private val XDG_BIN_DIRECTORY = "XDG_BIN_HOME"

View File

@ -76,6 +76,7 @@ class PortableDistributionManager(env: Environment)
engines = root / ENGINES_DIRECTORY,
bundle = None,
config = root / CONFIG_DIRECTORY,
runRoot = root,
locks = root / LOCK_DIRECTORY,
logs = root / LOG_DIRECTORY,
unsafeTemporaryDirectory = root / TMP_DIRECTORY,

View File

@ -0,0 +1,98 @@
package org.enso.librarymanager
import org.enso.distribution.DistributionManager
import org.enso.editions.{Editions, LibraryName, LibraryVersion}
import org.enso.librarymanager.local.LocalLibraryProvider
import org.enso.librarymanager.published.bundles.LocalReadOnlyRepository
import org.enso.librarymanager.published.cache.NoOpCache
import org.enso.librarymanager.published.{
DefaultPublishedLibraryProvider,
PublishedLibraryProvider
}
import java.nio.file.Path
/** A helper class for loading libraries.
*
* @param distributionManager a distribution manager
* @param engineDistributionRoot the root of the engine distribution that is
* being run, if applicable; it is used to make
* bundled libraries available
* @param edition the edition used in the project
* @param preferLocalLibraries project setting whether to use local libraries
*/
case class DefaultLibraryProvider(
distributionManager: DistributionManager,
engineDistributionRoot: Option[Path],
edition: Editions.ResolvedEdition,
preferLocalLibraries: Boolean
) extends ResolvingLibraryProvider {
private val localLibraryProvider =
LocalLibraryProvider.make(distributionManager)
private val resolver = LibraryResolver(localLibraryProvider)
// TODO [RW] actual cache that can download libraries will be implemented in #1772
private val primaryCache = new NoOpCache
private val additionalCaches = {
val bundleRoot = engineDistributionRoot.map { root =>
// TODO [RW] change this to sth like just `lib`
root.resolve("std-lib")
}
val locations =
bundleRoot.toList ++ distributionManager.auxiliaryLibraryCaches()
locations.map(new LocalReadOnlyRepository(_))
}
private val publishedLibraryProvider: PublishedLibraryProvider =
new DefaultPublishedLibraryProvider(primaryCache, additionalCaches)
/** 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.
*/
override def findLibrary(
libraryName: LibraryName
): Either[ResolvingLibraryProvider.Error, ResolvedLibrary] = {
val resolvedVersion = resolver
.resolveLibraryVersion(libraryName, edition, preferLocalLibraries)
resolvedVersion match {
case Left(reason) =>
Left(ResolvingLibraryProvider.Error.NotResolved(reason))
case Right(LibraryVersion.Local) =>
localLibraryProvider
.findLibrary(libraryName)
.map(ResolvedLibrary(libraryName, LibraryVersion.Local, _))
.toRight {
ResolvingLibraryProvider.Error.NotResolved(
LibraryResolutionError(
s"Edition configuration forces to use the local version, but " +
s"the `$libraryName` library is not present among local " +
s"libraries."
)
)
}
case Right(version @ LibraryVersion.Published(semver, repository)) =>
val dependencyResolver = { name: LibraryName =>
resolver
.resolveLibraryVersion(name, edition, preferLocalLibraries)
.toOption
}
publishedLibraryProvider
.findLibrary(
libraryName,
semver,
repository,
dependencyResolver
)
.map(ResolvedLibrary(libraryName, version, _))
.toEither
.left
.map(ResolvingLibraryProvider.Error.DownloadFailed)
}
}
}

View File

@ -1,61 +0,0 @@
package org.enso.librarymanager
import org.enso.distribution.DistributionManager
import org.enso.editions.{Editions, LibraryName, LibraryVersion}
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

@ -1,32 +0,0 @@
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,12 @@
package org.enso.librarymanager
import org.enso.editions.{LibraryName, LibraryVersion}
import java.nio.file.Path
/** Represents a resolved library that is located somewhere on the filesystem. */
case class ResolvedLibrary(
name: LibraryName,
version: LibraryVersion,
location: Path
)

View File

@ -0,0 +1,42 @@
package org.enso.librarymanager
import org.enso.editions.LibraryName
/** A helper class for resolving libraries. */
trait ResolvingLibraryProvider {
/** Resolves which library version should be used and finds its path within
* local libraries or the cache.
*
* If the library is not cached, it attempts to download it.
*
* @param name name of the library
* @return the resolved library containing the resulting version and path
*/
def findLibrary(
name: LibraryName
): Either[ResolvingLibraryProvider.Error, ResolvedLibrary]
}
object ResolvingLibraryProvider {
/** Indicates a failure to find a library. */
sealed trait Error
object Error {
/** Indicates that the library could not be resolved in the edition
* (for example it is not defined in that edition).
*/
case class NotResolved(details: LibraryResolutionError) extends Error
/** Indicates that a local library version was requested, but it did not
* exist on the library path.
*/
case object RequestedLocalLibraryDoesNotExist extends Error
/** Indicates that the library version was missing and had to be downloaded,
* but the download has failed.
*/
case class DownloadFailed(reason: Throwable) extends Error
}
}

View File

@ -1,21 +1,24 @@
package org.enso.librarymanager.local
import org.enso.distribution.DistributionManager
import org.enso.editions.LibraryName
import com.typesafe.scalalogging.Logger
import org.enso.distribution.FileSystem.PathSyntax
import org.enso.editions.LibraryName
import org.enso.logger.masking.MaskedPath
import java.nio.file.{Files, Path}
import scala.annotation.tailrec
/** A default implementation of [[LocalLibraryProvider]]. */
class DefaultLocalLibraryProvider(distributionManager: DistributionManager)
class DefaultLocalLibraryProvider(searchPaths: List[Path])
extends LocalLibraryProvider {
private val logger = Logger[DefaultLocalLibraryProvider]
/** @inheritdoc */
override def findLibrary(libraryName: LibraryName): Option[Path] =
findLibraryHelper(
libraryName,
distributionManager.paths.localLibrariesSearchPaths.toList
searchPaths
)
/** Searches through the available library paths, checking if any one of them contains the requested library.
@ -29,9 +32,19 @@ class DefaultLocalLibraryProvider(distributionManager: DistributionManager)
): Option[Path] = searchPaths match {
case head :: tail =>
val potentialPath = head / libraryName.namespace / libraryName.name
if (Files.exists(potentialPath) && Files.isDirectory(potentialPath))
if (Files.exists(potentialPath) && Files.isDirectory(potentialPath)) {
logger.trace(
s"Found a local $libraryName at " +
s"[${MaskedPath(potentialPath).applyMasking()}]."
)
Some(potentialPath)
else findLibraryHelper(libraryName, tail)
} else {
logger.trace(
s"Local library $libraryName not found at " +
s"[${MaskedPath(potentialPath).applyMasking()}]."
)
findLibraryHelper(libraryName, tail)
}
case Nil => None
}
}

View File

@ -19,5 +19,7 @@ object LocalLibraryProvider {
/** Creates a default [[LocalLibraryProvider]] from a [[DistributionManager]].
*/
def make(distributionManager: DistributionManager): LocalLibraryProvider =
new DefaultLocalLibraryProvider(distributionManager)
new DefaultLocalLibraryProvider(
distributionManager.paths.localLibrariesSearchPaths.toList
)
}

View File

@ -1,18 +1,41 @@
package org.enso.librarymanager.published
import nl.gn0s1s.bump.SemVer
import org.enso.distribution.DistributionManager
import org.enso.editions.{Editions, LibraryName, LibraryVersion}
import org.enso.librarymanager.LibraryResolutionResult
import org.enso.librarymanager.published.cache.{
LibraryCache,
ReadOnlyLibraryCache
}
import scala.annotation.nowarn
import java.nio.file.Path
import scala.annotation.tailrec
import scala.util.{Success, Try}
/** A default implementation of [[PublishedLibraryProvider]]. */
/** A default implementation of [[PublishedLibraryProvider]] which uses one
* primary cache to which it will download missing packages and auxiliary
* read-only caches which may provide additional libraries.
*/
class DefaultPublishedLibraryProvider(
@nowarn("msg=never used") distributionManager: DistributionManager
primaryCache: LibraryCache,
auxiliaryCaches: List[ReadOnlyLibraryCache]
) extends PublishedLibraryProvider {
// TODO [RW] This is just a stub and will be properly implemented in #1772.
private val caches: List[ReadOnlyLibraryCache] =
primaryCache :: auxiliaryCaches
@tailrec
private def findCached(
libraryName: LibraryName,
version: SemVer,
caches: List[ReadOnlyLibraryCache]
): Option[Path] = caches match {
case head :: tail =>
head.findCachedLibrary(libraryName, version) match {
case Some(found) => Some(found)
case None => findCached(libraryName, version, tail)
}
case Nil => None
}
/** @inheritdoc */
override def findLibrary(
@ -20,9 +43,15 @@ class DefaultPublishedLibraryProvider(
version: SemVer,
recommendedRepository: Editions.Repository,
dependencyResolver: LibraryName => Option[LibraryVersion]
): LibraryResolutionResult = LibraryResolutionResult.ResolutionFailure(
new NotImplementedError(
"TODO library management is not yet implemented"
)
)
): Try[Path] = {
val cached = findCached(libraryName, version, caches)
cached.map(Success(_)).getOrElse {
primaryCache.findOrInstallLibrary(
libraryName,
version,
recommendedRepository,
dependencyResolver
)
}
}
}

View File

@ -3,7 +3,9 @@ package org.enso.librarymanager.published
import nl.gn0s1s.bump.SemVer
import org.enso.editions.Editions.Repository
import org.enso.editions.{LibraryName, LibraryVersion}
import org.enso.librarymanager.LibraryResolutionResult
import java.nio.file.Path
import scala.util.Try
/** A provider of published libraries.
*
@ -23,5 +25,5 @@ trait PublishedLibraryProvider {
version: SemVer,
recommendedRepository: Repository,
dependencyResolver: LibraryName => Option[LibraryVersion]
): LibraryResolutionResult
): Try[Path]
}

View File

@ -0,0 +1,31 @@
package org.enso.librarymanager.published.bundles
import nl.gn0s1s.bump.SemVer
import org.enso.editions.LibraryName
import org.enso.librarymanager.published.cache.{
LibraryCache,
ReadOnlyLibraryCache
}
import java.nio.file.{Files, Path}
/** Implements a read-only cache backed by a repository on the local filesystem.
*
* This repository is either immutable (like the bundles) or user-managed (for
* custom solutions). In the first case synchronization is not necessary, in
* the second case it is impossible (as we have no authority over user's
* actions). So this class performs no synchronization and in the second case
* it is the user's case to not import libraries that are in the middle of
* being copied into this repository.
*/
class LocalReadOnlyRepository(root: Path) extends ReadOnlyLibraryCache {
/** @inheritdoc */
override def findCachedLibrary(
libraryName: LibraryName,
version: SemVer
): Option[Path] = {
val path = LibraryCache.resolvePath(root, libraryName, version)
if (Files.exists(path)) Some(path) else None
}
}

View File

@ -0,0 +1,69 @@
package org.enso.librarymanager.published.cache
import nl.gn0s1s.bump.SemVer
import org.enso.editions.{Editions, LibraryName, LibraryVersion}
import java.nio.file.Path
import scala.util.Try
/** A library cache that is also capable of downloading missing libraries (which
* will then be cached).
*/
trait LibraryCache extends ReadOnlyLibraryCache {
/** Returns the path to the library it is already cached.
*
* This method should not attempt to download the library if it is missing,
* because other providers may have it.
*
* As this repository is not immutable - new libraries may be added during
* the runtime, this method must too be aware of the concurrency - it should
* use locks to make sure that, if the library is currently being installed,
* it is not returned before the installation is complete (as otherwise the
* runtime could access an incompletely installed library which could lead to
* errors).
*/
override def findCachedLibrary(
libraryName: LibraryName,
version: SemVer
): Option[Path]
/** If the cache contains the library, it is returned immediately, otherwise,
* it tries to download the missing library.
*
* @param libraryName the name of the library to search for
* @param version the library version
* @param recommendedRepository the repository that should be used to
* download the library from, if it is missing
* @param dependencyResolver a function that will specify what versions of
* dependencies should be also downloaded when
* installing the missing library (if any)
* TODO [RW] the design of this function should be refined in #1772
* @return the path to the library or a failure if the library could not be
* installed
*/
def findOrInstallLibrary(
libraryName: LibraryName,
version: SemVer,
recommendedRepository: Editions.Repository,
dependencyResolver: LibraryName => Option[LibraryVersion]
): Try[Path]
}
object LibraryCache {
/** Finds a path to a particular library version inside of a local
* repository/cache according to the cache's directory structure.
*
* @param root path to the root of the repository
* @param libraryName name of the library
* @param version library version
* @return the path at which the specified library would be located in the
* repository
*/
def resolvePath(root: Path, libraryName: LibraryName, version: SemVer): Path =
root
.resolve(libraryName.namespace)
.resolve(libraryName.name)
.resolve(version.toString)
}

View File

@ -0,0 +1,30 @@
package org.enso.librarymanager.published.cache
import nl.gn0s1s.bump.SemVer
import org.enso.editions.{Editions, LibraryName, LibraryVersion}
import java.nio.file.Path
import scala.util.{Failure, Try}
/** A temporary cache that provides no libraries.
*
* This is a temporary poly-fill which will later be replaced when the
* downloading mechanism is implemented.
*/
class NoOpCache extends LibraryCache {
/** @inheritdoc */
override def findCachedLibrary(
libraryName: LibraryName,
version: SemVer
): Option[Path] = None
/** @inheritdoc */
override def findOrInstallLibrary(
libraryName: LibraryName,
version: SemVer,
recommendedRepository: Editions.Repository,
dependencyResolver: LibraryName => Option[LibraryVersion]
): Try[Path] = Failure(
new NotImplementedError("Downloading libraries is not yet implemented.")
)
}

View File

@ -0,0 +1,19 @@
package org.enso.librarymanager.published.cache
import nl.gn0s1s.bump.SemVer
import org.enso.editions.LibraryName
import java.nio.file.Path
/** A read-only cache may contain some pre-defined set of libraries, but it does
* not necessarily install any additional libraries.
*
* An example of a read-only cache are libraries bundled with the engine.
*/
trait ReadOnlyLibraryCache {
/** Locates the library in the cache and returns the path to its root if it
* has been found.
*/
def findCachedLibrary(libraryName: LibraryName, version: SemVer): Option[Path]
}

View File

@ -0,0 +1,36 @@
package org.slf4j.impl;
import org.enso.truffleloggerwrapper.TruffleLoggerWrapperFactory;
import org.slf4j.ILoggerFactory;
/**
* Binds the SLF4J logger instance within the runtime to a logger which wraps the TruffleLogger.
*
* <p>This way, the standard SLF4J interface that is used in other subprojects, can also be used
* within the runtime and its log messages are correctly passed to the TruffleLogger. Thus libraries
* that are used both inside and outside of runtime can keep a simple interface.
*
* <p>The public interface of this class must conform to what is expected by an SLF4J backend. See
* slf4j-simple for reference.
*/
public class StaticLoggerBinder {
/** Should be in sync with `slf4jVersion` in `build.sbt`. */
public static String REQUESTED_API_VERSION = "1.7.30";
private static final StaticLoggerBinder singleton = new StaticLoggerBinder();
public static StaticLoggerBinder getSingleton() {
return singleton;
}
private final TruffleLoggerWrapperFactory factory = new TruffleLoggerWrapperFactory();
private final String factoryClassStr = TruffleLoggerWrapperFactory.class.getName();
public ILoggerFactory getLoggerFactory() {
return factory;
}
public String getLoggerFactoryClassStr() {
return factoryClassStr;
}
}

View File

@ -0,0 +1,33 @@
package org.slf4j.impl;
import org.slf4j.helpers.NOPMDCAdapter;
import org.slf4j.spi.MDCAdapter;
/**
* Provides a no-op MDC adapter for the SLF4J backend.
*
* <p>MDC handling is an optional SLF4J feature and currently the logging service does not support
* it, so it provides a no-op adapter.
*
* <p>The public interface of this class must conform to what is expected by an SLF4J backend. See
* slf4j-simple for reference.
*/
public class StaticMDCBinder {
private static final StaticMDCBinder singleton = new StaticMDCBinder();
public static StaticMDCBinder getSingleton() {
return singleton;
}
private final MDCAdapter adapter = new NOPMDCAdapter();
private final String adapterClassStr = NOPMDCAdapter.class.getName();
public MDCAdapter getMDCA() {
return adapter;
}
public String getMDCAdapterClassStr() {
return adapterClassStr;
}
}

View File

@ -0,0 +1,30 @@
package org.slf4j.impl;
import org.slf4j.IMarkerFactory;
import org.slf4j.helpers.BasicMarkerFactory;
/**
* Provides a simple marker factory for the SLF4J backend.
*
* <p>The public interface of this class must conform to what is expected by an SLF4J backend. See
* slf4j-simple for reference.
*/
public class StaticMarkerBinder {
private static final StaticMarkerBinder singleton = new StaticMarkerBinder();
public static StaticMarkerBinder getSingleton() {
return singleton;
}
private final IMarkerFactory markerFactory = new BasicMarkerFactory();
private final String markerFactoryClassStr = BasicMarkerFactory.class.getName();
public IMarkerFactory getMarkerFactory() {
return markerFactory;
}
public String getMarkerFactoryClassStr() {
return markerFactoryClassStr;
}
}

View File

@ -0,0 +1,319 @@
package org.enso.truffleloggerwrapper
import com.oracle.truffle.api.TruffleLogger
import org.enso.logger.masking.Masking
import org.enso.polyglot.LanguageInfo
import org.slf4j.helpers.MessageFormatter
import org.slf4j.{Logger, Marker}
import java.util.logging.Level
import scala.annotation.unused
/** A wrapper around [[TruffleLogger]] that abides by the SLF4J's [[Logger]]
* interface.
*
* It is used so that libraries which are used both inside and outside of the
* runtime can simply use the SLF4J API and the log messages are passed to the
* correct backend (in the case of the runtime, they are forwarded to the
* [[TruffleLogger]]).
*/
class TruffleLoggerWrapper(name: String, masking: Masking) extends Logger {
final private val underlying = TruffleLogger.getLogger(LanguageInfo.ID, name)
override def getName: String = underlying.getName
private def isEnabled(level: Level): Boolean =
underlying.isLoggable(level)
private def log(
level: Level,
msg: String
): Unit = {
if (isEnabled(level)) {
underlying.log(level, msg)
}
}
private def log(
level: Level,
format: String,
arg: AnyRef
): Unit = {
if (isEnabled(level)) {
val maskedArg = masking.mask(arg)
val fp = MessageFormatter.format(format, maskedArg)
if (fp.getThrowable == null) {
underlying.log(level, fp.getMessage)
} else {
underlying.log(level, fp.getMessage, fp.getThrowable)
}
}
}
private def log(
level: Level,
format: String,
arg1: AnyRef,
arg2: AnyRef
): Unit = {
if (isEnabled(level)) {
val maskedArg1 = masking.mask(arg1)
val maskedArg2 = masking.mask(arg2)
val fp = MessageFormatter.format(format, maskedArg1, maskedArg2)
if (fp.getThrowable == null) {
underlying.log(level, fp.getMessage)
} else {
underlying.log(level, fp.getMessage, fp.getThrowable)
}
}
}
private def log(
level: Level,
format: String,
args: Seq[AnyRef]
): Unit = {
if (isEnabled(level)) {
val maskedArgs = args.map(masking.mask)
val fp = MessageFormatter.arrayFormat(format, maskedArgs.toArray)
if (fp.getThrowable == null) {
underlying.log(level, fp.getMessage)
} else {
underlying.log(level, fp.getMessage, fp.getThrowable)
}
}
}
private def log(
level: Level,
msg: String,
throwable: Throwable
): Unit = {
if (isEnabled(level)) {
underlying.log(level, msg, throwable)
}
}
override def isTraceEnabled: Boolean = isEnabled(Level.FINER)
override def trace(msg: String): Unit = log(Level.FINER, msg)
override def trace(format: String, arg: AnyRef): Unit =
log(Level.FINER, format, arg)
override def trace(format: String, arg1: AnyRef, arg2: AnyRef): Unit =
log(Level.FINER, format, arg1, arg2)
override def trace(format: String, arguments: AnyRef*): Unit =
log(Level.FINER, format, arguments)
override def trace(msg: String, t: Throwable): Unit = log(Level.FINER, msg, t)
override def isTraceEnabled(@unused marker: Marker): Boolean =
isEnabled(Level.FINER)
override def trace(@unused marker: Marker, msg: String): Unit =
log(Level.FINER, msg)
override def trace(
@unused marker: Marker,
format: String,
arg: AnyRef
): Unit =
log(Level.FINER, format, arg)
override def trace(
@unused marker: Marker,
format: String,
arg1: AnyRef,
arg2: AnyRef
): Unit = log(Level.FINER, format, arg1, arg2)
override def trace(
@unused marker: Marker,
format: String,
argArray: AnyRef*
): Unit =
log(Level.FINER, format, argArray)
override def trace(@unused marker: Marker, msg: String, t: Throwable): Unit =
log(Level.FINER, msg, t)
override def isDebugEnabled: Boolean = isEnabled(Level.FINE)
override def debug(msg: String): Unit = log(Level.FINE, msg)
override def debug(format: String, arg: AnyRef): Unit =
log(Level.FINE, format, arg)
override def debug(format: String, arg1: AnyRef, arg2: AnyRef): Unit =
log(Level.FINE, format, arg1, arg2)
override def debug(format: String, arguments: AnyRef*): Unit =
log(Level.FINE, format, arguments)
override def debug(msg: String, t: Throwable): Unit = log(Level.FINE, msg, t)
override def isDebugEnabled(@unused marker: Marker): Boolean = isEnabled(
Level.FINE
)
override def debug(@unused marker: Marker, msg: String): Unit =
log(Level.FINE, msg)
override def debug(
@unused marker: Marker,
format: String,
arg: AnyRef
): Unit =
log(Level.FINE, format, arg)
override def debug(
@unused marker: Marker,
format: String,
arg1: AnyRef,
arg2: AnyRef
): Unit = log(Level.FINE, format, arg1, arg2)
override def debug(
@unused marker: Marker,
format: String,
arguments: AnyRef*
): Unit =
log(Level.FINE, format, arguments)
override def debug(@unused marker: Marker, msg: String, t: Throwable): Unit =
log(Level.FINE, msg, t)
override def isInfoEnabled: Boolean = isEnabled(Level.INFO)
override def info(msg: String): Unit = log(Level.INFO, msg)
override def info(format: String, arg: AnyRef): Unit =
log(Level.INFO, format, arg)
override def info(format: String, arg1: AnyRef, arg2: AnyRef): Unit =
log(Level.INFO, format, arg1, arg2)
override def info(format: String, arguments: AnyRef*): Unit =
log(Level.INFO, format, arguments)
override def info(msg: String, t: Throwable): Unit = log(Level.INFO, msg, t)
override def isInfoEnabled(@unused marker: Marker): Boolean = isEnabled(
Level.INFO
)
override def info(@unused marker: Marker, msg: String): Unit =
log(Level.INFO, msg)
override def info(@unused marker: Marker, format: String, arg: AnyRef): Unit =
log(Level.INFO, format, arg)
override def info(
@unused marker: Marker,
format: String,
arg1: AnyRef,
arg2: AnyRef
): Unit = log(Level.INFO, format, arg1, arg2)
override def info(
@unused marker: Marker,
format: String,
arguments: AnyRef*
): Unit =
log(Level.INFO, format, arguments)
override def info(@unused marker: Marker, msg: String, t: Throwable): Unit =
log(Level.INFO, msg, t)
override def isWarnEnabled: Boolean = isEnabled(Level.WARNING)
override def warn(msg: String): Unit = log(Level.WARNING, msg)
override def warn(format: String, arg: AnyRef): Unit =
log(Level.WARNING, format, arg)
override def warn(format: String, arguments: AnyRef*): Unit =
log(Level.WARNING, format, arguments)
override def warn(format: String, arg1: AnyRef, arg2: AnyRef): Unit =
log(Level.WARNING, format, arg1, arg2)
override def warn(msg: String, t: Throwable): Unit =
log(Level.WARNING, msg, t)
override def isWarnEnabled(@unused marker: Marker): Boolean = isEnabled(
Level.WARNING
)
override def warn(@unused marker: Marker, msg: String): Unit =
log(Level.WARNING, msg)
override def warn(@unused marker: Marker, format: String, arg: AnyRef): Unit =
log(Level.WARNING, format, arg)
override def warn(
@unused marker: Marker,
format: String,
arg1: AnyRef,
arg2: AnyRef
): Unit = log(Level.WARNING, format, arg1, arg2)
override def warn(
@unused marker: Marker,
format: String,
arguments: AnyRef*
): Unit =
log(Level.WARNING, format, arguments)
override def warn(@unused marker: Marker, msg: String, t: Throwable): Unit =
log(Level.WARNING, msg, t)
override def isErrorEnabled: Boolean = isEnabled(Level.SEVERE)
override def error(msg: String): Unit = log(Level.SEVERE, msg)
override def error(format: String, arg: AnyRef): Unit =
log(Level.SEVERE, format, arg)
override def error(format: String, arg1: AnyRef, arg2: AnyRef): Unit =
log(Level.SEVERE, format, arg1, arg2)
override def error(format: String, arguments: AnyRef*): Unit =
log(Level.SEVERE, format, arguments)
override def error(msg: String, t: Throwable): Unit =
log(Level.SEVERE, msg, t)
override def isErrorEnabled(@unused marker: Marker): Boolean = isEnabled(
Level.SEVERE
)
override def error(@unused marker: Marker, msg: String): Unit =
log(Level.SEVERE, msg)
override def error(
@unused marker: Marker,
format: String,
arg: AnyRef
): Unit =
log(Level.SEVERE, format, arg)
override def error(
@unused marker: Marker,
format: String,
arg1: AnyRef,
arg2: AnyRef
): Unit = log(Level.SEVERE, format, arg1, arg2)
override def error(
@unused marker: Marker,
format: String,
arguments: AnyRef*
): Unit =
log(Level.SEVERE, format, arguments)
override def error(@unused marker: Marker, msg: String, t: Throwable): Unit =
log(Level.SEVERE, msg, t)
}

View File

@ -0,0 +1,12 @@
package org.enso.truffleloggerwrapper
import org.enso.logger.masking.Masking
import org.slf4j.ILoggerFactory
/** A factory for [[TruffleLoggerWrapper]]. */
class TruffleLoggerWrapperFactory extends ILoggerFactory {
/** @inheritdoc */
override def getLogger(name: String) =
new TruffleLoggerWrapper(name, Masking())
}

View File

@ -16,7 +16,8 @@ class HomeManager[F](languageHomePath: F, implicit val fs: FileSystem[F]) {
val packageManager = new PackageManager[F]()
val rootPath: F = languageHomePath.getParent
val libPath: F = rootPath.getChild("std-lib")
// TODO [RW] rename to lib or sth more general than just std
val libPath: F = rootPath.getChild("std-lib")
/** @return a stream of packages found in the `std-lib` home directory.
*/

View File

@ -1,7 +1,7 @@
package org.enso.pkg
import cats.Show
import org.enso.editions.{DefaultEnsoVersion, EnsoVersion}
import org.enso.editions.{DefaultEnsoVersion, EnsoVersion, LibraryName}
import org.enso.filesystem.FileSystem
import org.enso.pkg.validation.NameValidation
@ -124,6 +124,9 @@ case class Package[F](
def namespace: String = config.namespace
/** A [[LibraryName]] associated with the package. */
def libraryName: LibraryName = LibraryName(config.namespace, config.name)
/** Parses a file path into a qualified module name belonging to this
* package.
*

View File

@ -89,6 +89,7 @@ object ExecutorWithUnlimitedPool extends LanguageServerExecutor {
)
val runner = new Runner(
runtimeVersionManager = versionManager,
distributionManager = distributionConfiguration.distributionManager,
globalConfigurationManager = configurationManager,
editionManager = distributionConfiguration.editionManager,
environment = distributionConfiguration.environment,

View File

@ -45,6 +45,7 @@ class ProjectCreationService[
val runner =
new Runner(
runtimeVersionManager = versionManager,
distributionManager = distributionConfiguration.distributionManager,
globalConfigurationManager = configurationManager,
editionManager = distributionConfiguration.editionManager,
environment = distributionConfiguration.environment,

View File

@ -860,12 +860,13 @@ class RuntimeVersionManager(
.getOrElse("no runtime found")
val broken = if (engine.isMarkedBroken) " (broken)" else ""
s" - Enso ${engine.version}$broken [runtime: $runtimeName] " +
s"[location: ${MaskedPath(engine.path)}]"
s"[location: ${MaskedPath(engine.path).applyMasking()}]"
}
val runtimes =
for (runtime <- listInstalledGraalRuntimes())
yield s" - $runtime [location: ${MaskedPath(runtime.path)}]"
yield s" - $runtime [location: " +
s"${MaskedPath(runtime.path).applyMasking()}]"
logger.trace(
s"Installed engines (${engines.length}):\n${engines.mkString("\n")}\n\n" +

View File

@ -3,7 +3,7 @@ package org.enso.runtimeversionmanager.runner
import akka.http.scaladsl.model.Uri
import com.typesafe.scalalogging.Logger
import nl.gn0s1s.bump.SemVer
import org.enso.distribution.{EditionManager, Environment}
import org.enso.distribution.{DistributionManager, EditionManager, Environment}
import org.enso.editions.{DefaultEnsoVersion, SemVerEnsoVersion}
import org.enso.logger.masking.MaskedString
import org.enso.loggingservice.LogLevel
@ -26,6 +26,7 @@ import scala.util.Try
*/
class Runner(
runtimeVersionManager: RuntimeVersionManager,
distributionManager: DistributionManager,
globalConfigurationManager: GlobalConfigurationManager,
editionManager: EditionManager,
environment: Environment,
@ -179,9 +180,12 @@ class Runner(
val command = Seq(javaCommand.executableName) ++
jvmArguments ++ loggingConnectionArguments ++ runSettings.runnerArguments
// TODO [RW] set the paths from DistributionManager so that the engine can use the simple distribution resolution logic
val distributionSettings =
distributionManager.getEnvironmentToInheritSettings
val extraEnvironmentOverrides =
javaCommand.javaHomeOverride.map("JAVA_HOME" -> _).toSeq
javaCommand.javaHomeOverride
.map("JAVA_HOME" -> _)
.toSeq ++ distributionSettings.toSeq
action(Command(command, extraEnvironmentOverrides))
}

View File

@ -1 +0,0 @@
Copyright (c) 2004-2011 QOS.ch

View File

@ -1,3 +1,3 @@
362FC1C027AFBFD6F5AD317AA49A3CC4DBB8DF5F64F03F7F421DBEC5D84F00D1
00E5CD7BD57C6E26B84CA9835FE8ECE2239DEF94E8FBB719A35BA909079ADEA4
167455C7D1FE65E7901D5613C3DBB3158CE003D45DE21D8D085815A2D20B317C
64FE86A276F737CE2B4D352D8F35F790CA0DA18F329A48A467F34FC9C3CAF07D
0

View File

@ -1,3 +1,3 @@
0BA0D3694722E724BABC3D4D860FA5D11DA541B20632F18175E1F45BF44DE717
378F1F6DE5E96E55648F0B31C20AF99F1C9190926BE6429B0FC8BE7CAF9E6E47
B7948AB0E996317E7F073260BA28A63D14215436079C09E793F803CF999D607C
0