mirror of
https://github.com/enso-org/enso.git
synced 2024-12-23 18:34:03 +03:00
Moving PackageRepository.Default into its own EnsoPackageRepository (#7395)
This commit is contained in:
parent
4b5a2e2176
commit
f2c46428bd
@ -1,28 +1,30 @@
|
|||||||
package org.enso.compiler;
|
package org.enso.compiler;
|
||||||
|
|
||||||
import buildinfo.Info;
|
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
|
||||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
||||||
import com.oracle.truffle.api.TruffleFile;
|
|
||||||
import com.oracle.truffle.api.TruffleLogger;
|
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.ObjectInputStream;
|
import java.io.ObjectInputStream;
|
||||||
import java.io.ObjectOutputStream;
|
import java.io.ObjectOutputStream;
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.enso.compiler.data.BindingsMap;
|
import org.enso.compiler.data.BindingsMap;
|
||||||
import org.enso.editions.LibraryName;
|
import org.enso.editions.LibraryName;
|
||||||
import org.enso.interpreter.runtime.EnsoContext;
|
import org.enso.interpreter.runtime.EnsoContext;
|
||||||
import org.enso.pkg.QualifiedName;
|
import org.enso.pkg.QualifiedName;
|
||||||
import org.enso.pkg.SourceFile;
|
import org.enso.pkg.SourceFile;
|
||||||
import scala.collection.immutable.Map;
|
|
||||||
|
|
||||||
import java.io.Serializable;
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
import java.util.List;
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||||
import java.util.Optional;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import java.util.logging.Level;
|
import com.oracle.truffle.api.TruffleFile;
|
||||||
|
import com.oracle.truffle.api.TruffleLogger;
|
||||||
|
|
||||||
|
import buildinfo.Info;
|
||||||
|
import scala.collection.immutable.Map;
|
||||||
|
|
||||||
public final class ImportExportCache extends Cache<ImportExportCache.CachedBindings, ImportExportCache.Metadata> {
|
public final class ImportExportCache extends Cache<ImportExportCache.CachedBindings, ImportExportCache.Metadata> {
|
||||||
|
|
||||||
@ -125,11 +127,12 @@ public final class ImportExportCache extends Cache<ImportExportCache.CachedBindi
|
|||||||
}
|
}
|
||||||
|
|
||||||
// CachedBindings is not a record **on purpose**. There appears to be a Frgaal bug leading to invalid compilation error.
|
// CachedBindings is not a record **on purpose**. There appears to be a Frgaal bug leading to invalid compilation error.
|
||||||
static class CachedBindings {
|
public static final class CachedBindings {
|
||||||
private final LibraryName _libraryName;
|
private final LibraryName _libraryName;
|
||||||
private final MapToBindings _bindings;
|
private final MapToBindings _bindings;
|
||||||
private final Optional<List<SourceFile<TruffleFile>>> _sources;
|
private final Optional<List<SourceFile<TruffleFile>>> _sources;
|
||||||
public CachedBindings(LibraryName libraryName, MapToBindings bindings, Optional<List<SourceFile<TruffleFile>>> sources) {
|
|
||||||
|
CachedBindings(LibraryName libraryName, MapToBindings bindings, Optional<List<SourceFile<TruffleFile>>> sources) {
|
||||||
this._libraryName = libraryName;
|
this._libraryName = libraryName;
|
||||||
this._bindings = bindings;
|
this._bindings = bindings;
|
||||||
this._sources = sources;
|
this._sources = sources;
|
||||||
|
@ -156,7 +156,7 @@ public class EnsoContext {
|
|||||||
var resourceManager = new org.enso.distribution.locking.ResourceManager(lockManager);
|
var resourceManager = new org.enso.distribution.locking.ResourceManager(lockManager);
|
||||||
|
|
||||||
packageRepository =
|
packageRepository =
|
||||||
PackageRepository.initializeRepository(
|
DefaultPackageRepository.initializeRepository(
|
||||||
OptionConverters.toScala(projectPackage),
|
OptionConverters.toScala(projectPackage),
|
||||||
OptionConverters.toScala(languageHome),
|
OptionConverters.toScala(languageHome),
|
||||||
OptionConverters.toScala(editionOverride),
|
OptionConverters.toScala(editionOverride),
|
||||||
|
@ -1,41 +1,12 @@
|
|||||||
package org.enso.compiler
|
package org.enso.compiler
|
||||||
|
|
||||||
import com.oracle.truffle.api.TruffleFile
|
import com.oracle.truffle.api.TruffleFile
|
||||||
import com.typesafe.scalalogging.Logger
|
import org.enso.editions.LibraryName
|
||||||
import org.apache.commons.lang3.StringUtils
|
import org.enso.interpreter.runtime.Module
|
||||||
import org.enso.distribution.locking.ResourceManager
|
import org.enso.pkg.{ComponentGroups, Package}
|
||||||
import org.enso.distribution.{DistributionManager, LanguageHome}
|
|
||||||
import org.enso.editions.updater.EditionManager
|
|
||||||
import org.enso.editions.{DefaultEdition, 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.{EnsoContext, Module}
|
|
||||||
import org.enso.librarymanager.published.repository.LibraryManifest
|
|
||||||
import org.enso.librarymanager.resolved.LibraryRoot
|
|
||||||
import org.enso.librarymanager.{
|
|
||||||
DefaultLibraryProvider,
|
|
||||||
ResolvingLibraryProvider
|
|
||||||
}
|
|
||||||
import org.enso.logger.masking.MaskedPath
|
|
||||||
import org.enso.pkg.{
|
|
||||||
Component,
|
|
||||||
ComponentGroup,
|
|
||||||
ComponentGroups,
|
|
||||||
ExtendedComponentGroup,
|
|
||||||
Package,
|
|
||||||
PackageManager,
|
|
||||||
QualifiedName,
|
|
||||||
SourceFile
|
|
||||||
}
|
|
||||||
import org.enso.text.buffer.Rope
|
|
||||||
import org.enso.polyglot.CompilationStage
|
|
||||||
|
|
||||||
import java.nio.file.Path
|
|
||||||
import scala.collection.immutable.ListSet
|
import scala.collection.immutable.ListSet
|
||||||
import scala.jdk.OptionConverters.RichOption
|
import scala.jdk.OptionConverters.RichOption
|
||||||
import scala.jdk.CollectionConverters.{IterableHasAsJava, SeqHasAsJava}
|
|
||||||
import scala.util.{Failure, Try, Using}
|
|
||||||
|
|
||||||
/** Manages loaded packages and modules. */
|
/** Manages loaded packages and modules. */
|
||||||
trait PackageRepository {
|
trait PackageRepository {
|
||||||
@ -181,609 +152,4 @@ object PackageRepository {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 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(
|
|
||||||
libraryProvider: ResolvingLibraryProvider,
|
|
||||||
context: EnsoContext,
|
|
||||||
builtins: Builtins,
|
|
||||||
notificationHandler: NotificationHandler
|
|
||||||
) extends PackageRepository {
|
|
||||||
|
|
||||||
private val logger = Logger[Default]
|
|
||||||
|
|
||||||
implicit private val fs: TruffleFileSystem = new TruffleFileSystem
|
|
||||||
private val packageManager = new PackageManager[TruffleFile]
|
|
||||||
private var projectPackage: Option[Package[TruffleFile]] = None
|
|
||||||
|
|
||||||
/** 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.
|
|
||||||
*/
|
|
||||||
private val loadedPackages
|
|
||||||
: collection.mutable.Map[LibraryName, Option[Package[TruffleFile]]] = {
|
|
||||||
val builtinsName = LibraryName(Builtins.NAMESPACE, Builtins.PACKAGE_NAME)
|
|
||||||
collection.mutable.LinkedHashMap(builtinsName -> None)
|
|
||||||
}
|
|
||||||
|
|
||||||
/** The mapping containing loaded modules.
|
|
||||||
*
|
|
||||||
* We use [[String]] as the key as we often index into this map based on
|
|
||||||
* qualified names that come from interop (via
|
|
||||||
* [[org.enso.interpreter.runtime.scope.TopLevelScope]]). These arrive as
|
|
||||||
* Strings, and constantly converting them into [[QualifiedName]]s would
|
|
||||||
* add more overhead than is probably necessary.
|
|
||||||
*/
|
|
||||||
private val loadedModules: collection.concurrent.Map[String, Module] =
|
|
||||||
collection.concurrent.TrieMap(Builtins.MODULE_NAME -> builtins.getModule)
|
|
||||||
|
|
||||||
/** The mapping containing loaded component groups.
|
|
||||||
*
|
|
||||||
* It should be modified and read only from within synchronized sections.
|
|
||||||
* The component mapping is added to the collection after ensuring that the
|
|
||||||
* corresponding library was loaded.
|
|
||||||
*/
|
|
||||||
private val loadedComponents
|
|
||||||
: collection.mutable.Map[LibraryName, ComponentGroups] = {
|
|
||||||
val builtinsName = LibraryName(Builtins.NAMESPACE, Builtins.PACKAGE_NAME)
|
|
||||||
collection.mutable.LinkedHashMap(builtinsName -> ComponentGroups.empty)
|
|
||||||
}
|
|
||||||
|
|
||||||
/** The mapping between the library and its cached bindings, if already laoded. */
|
|
||||||
private val loadedLibraryBindings: collection.mutable.Map[
|
|
||||||
LibraryName,
|
|
||||||
ImportExportCache.CachedBindings
|
|
||||||
] =
|
|
||||||
collection.mutable.LinkedHashMap()
|
|
||||||
|
|
||||||
private def getComponentModules: ListSet[Module] = {
|
|
||||||
val modules = for {
|
|
||||||
componentGroups <- loadedComponents.values
|
|
||||||
newComponents = componentGroups.newGroups.flatMap(_.exports)
|
|
||||||
extendedComponents = componentGroups.extendedGroups.flatMap(_.exports)
|
|
||||||
component <- newComponents ++ extendedComponents
|
|
||||||
module <- findComponentModule(component)
|
|
||||||
} yield module
|
|
||||||
modules.to(ListSet)
|
|
||||||
}
|
|
||||||
|
|
||||||
private def findComponentModule(component: Component): Option[Module] = {
|
|
||||||
def mkModuleName(path: Array[String]): String =
|
|
||||||
path.mkString(LibraryName.separator.toString)
|
|
||||||
@scala.annotation.tailrec
|
|
||||||
def go(path: Array[String]): Option[Module] =
|
|
||||||
if (path.isEmpty) None
|
|
||||||
else {
|
|
||||||
loadedModules.get(mkModuleName(path)) match {
|
|
||||||
case Some(module) => Some(module)
|
|
||||||
case None => go(path.init)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
go(component.name.split(LibraryName.separator))
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @inheritdoc */
|
|
||||||
override def getModuleMap: ModuleMap = loadedModules
|
|
||||||
|
|
||||||
/** @inheritdoc */
|
|
||||||
override def freezeModuleMap: FrozenModuleMap = loadedModules.toMap
|
|
||||||
|
|
||||||
/** @inheritdoc */
|
|
||||||
override def getComponents: ComponentsMap =
|
|
||||||
this.synchronized {
|
|
||||||
loadedComponents.toMap
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @inheritdoc */
|
|
||||||
override def getPendingModules: ListSet[Module] =
|
|
||||||
this.synchronized {
|
|
||||||
for {
|
|
||||||
module <- getComponentModules
|
|
||||||
isCompiled =
|
|
||||||
module.getCompilationStage.isAtLeast(
|
|
||||||
CompilationStage.AFTER_CODEGEN
|
|
||||||
)
|
|
||||||
if !isCompiled
|
|
||||||
} yield module
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @inheritdoc */
|
|
||||||
override def registerMainProjectPackage(
|
|
||||||
libraryName: LibraryName,
|
|
||||||
pkg: Package[TruffleFile]
|
|
||||||
): Unit = {
|
|
||||||
projectPackage = Some(pkg)
|
|
||||||
registerPackageInternal(
|
|
||||||
libraryName = libraryName,
|
|
||||||
pkg = pkg,
|
|
||||||
libraryVersion = LibraryVersion.Local,
|
|
||||||
isLibrary = false
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @inheritdoc */
|
|
||||||
override def getMainProjectPackage: Option[Package[TruffleFile]] = {
|
|
||||||
projectPackage
|
|
||||||
}
|
|
||||||
|
|
||||||
private def registerPackageInternal(
|
|
||||||
libraryName: LibraryName,
|
|
||||||
libraryVersion: LibraryVersion,
|
|
||||||
pkg: Package[TruffleFile],
|
|
||||||
isLibrary: Boolean
|
|
||||||
): Unit = {
|
|
||||||
val extensions = pkg.listPolyglotExtensions("java")
|
|
||||||
extensions.foreach(context.getEnvironment.addToHostClassPath)
|
|
||||||
|
|
||||||
val (regularModules, syntheticModulesMetadata) = pkg
|
|
||||||
.listSources()
|
|
||||||
.map(srcFile =>
|
|
||||||
(
|
|
||||||
new Module(srcFile.qualifiedName, pkg, srcFile.file),
|
|
||||||
inferSyntheticModules(srcFile)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.unzip
|
|
||||||
|
|
||||||
regularModules.foreach(registerModule)
|
|
||||||
|
|
||||||
syntheticModulesMetadata.flatten
|
|
||||||
.groupMap(_._1)(v => (v._2, v._3))
|
|
||||||
.foreach { case (qName, modulesWithSources) =>
|
|
||||||
val source = modulesWithSources
|
|
||||||
.map(_._2)
|
|
||||||
.foldLeft("")(_ ++ "\n" ++ _)
|
|
||||||
registerSyntheticModule(
|
|
||||||
Module.synthetic(
|
|
||||||
qName,
|
|
||||||
pkg,
|
|
||||||
Rope(source)
|
|
||||||
),
|
|
||||||
modulesWithSources.map(_._1)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isLibrary) {
|
|
||||||
val root = Path.of(pkg.root.toString)
|
|
||||||
notificationHandler.addedLibrary(libraryName, libraryVersion, root)
|
|
||||||
}
|
|
||||||
|
|
||||||
loadedPackages.put(libraryName, Some(pkg))
|
|
||||||
}
|
|
||||||
|
|
||||||
/** For any given source file, infer data necessary to generate synthetic modules as well as their contents.
|
|
||||||
* E.g., for A/B/C.enso it infers modules
|
|
||||||
* - A.B that exports A.B.C
|
|
||||||
* - A that exports A.B
|
|
||||||
*
|
|
||||||
* @param srcFile Enso source file to consider
|
|
||||||
* @return a list of triples representing the name of submodule along the path, what submodule it exports and its contents
|
|
||||||
*/
|
|
||||||
private def inferSyntheticModules(
|
|
||||||
srcFile: SourceFile[TruffleFile]
|
|
||||||
): List[(QualifiedName, QualifiedName, String)] = {
|
|
||||||
def listAllIntermediateModules(
|
|
||||||
namespace: String,
|
|
||||||
name: String,
|
|
||||||
elements: List[String],
|
|
||||||
exportItem: String
|
|
||||||
): List[(QualifiedName, QualifiedName, String)] = {
|
|
||||||
elements match {
|
|
||||||
case Nil =>
|
|
||||||
Nil
|
|
||||||
case lastModuleName :: parts =>
|
|
||||||
val pathElems = elements.reverse
|
|
||||||
val modName =
|
|
||||||
s"${namespace}.$name.${pathElems.mkString(".")}.$exportItem"
|
|
||||||
val modSource =
|
|
||||||
s"""|import $modName
|
|
||||||
|export $modName
|
|
||||||
|""".stripMargin
|
|
||||||
val syntheticModuleInfo = (
|
|
||||||
QualifiedName(namespace :: name :: parts.reverse, lastModuleName),
|
|
||||||
QualifiedName(namespace :: name :: pathElems, exportItem),
|
|
||||||
modSource
|
|
||||||
)
|
|
||||||
syntheticModuleInfo :: listAllIntermediateModules(
|
|
||||||
namespace,
|
|
||||||
name,
|
|
||||||
parts,
|
|
||||||
lastModuleName
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
srcFile.qualifiedName.path match {
|
|
||||||
case namespace :: name :: rest =>
|
|
||||||
listAllIntermediateModules(
|
|
||||||
namespace,
|
|
||||||
name,
|
|
||||||
rest.reverse,
|
|
||||||
srcFile.qualifiedName.item
|
|
||||||
)
|
|
||||||
case _ =>
|
|
||||||
Nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** This package modifies the [[loadedPackages]], so it should be only
|
|
||||||
* called from within synchronized sections.
|
|
||||||
*/
|
|
||||||
private def loadPackage(
|
|
||||||
libraryName: LibraryName,
|
|
||||||
libraryVersion: LibraryVersion,
|
|
||||||
root: LibraryRoot
|
|
||||||
): Either[Error, Package[TruffleFile]] = Try {
|
|
||||||
logger.debug(
|
|
||||||
s"Loading library $libraryName from " +
|
|
||||||
s"[${MaskedPath(root.location).applyMasking()}]."
|
|
||||||
)
|
|
||||||
val rootFile = context.getEnvironment.getInternalTruffleFile(
|
|
||||||
root.location.toAbsolutePath.normalize.toString
|
|
||||||
)
|
|
||||||
val pkg = packageManager.loadPackage(rootFile).get
|
|
||||||
registerPackageInternal(
|
|
||||||
libraryName = libraryName,
|
|
||||||
libraryVersion = libraryVersion,
|
|
||||||
pkg = pkg,
|
|
||||||
isLibrary = true
|
|
||||||
)
|
|
||||||
pkg
|
|
||||||
}.toEither.left.map { error => Error.PackageLoadingError(error.getMessage) }
|
|
||||||
|
|
||||||
/** @inheritdoc */
|
|
||||||
override def initialize(): Either[Error, Unit] =
|
|
||||||
this.synchronized {
|
|
||||||
val unprocessedPackages =
|
|
||||||
loadedPackages.keySet
|
|
||||||
.diff(loadedComponents.keySet)
|
|
||||||
.flatMap(loadedPackages(_))
|
|
||||||
unprocessedPackages.foldLeft[Either[Error, Unit]](Right(())) {
|
|
||||||
(accumulator, pkg) =>
|
|
||||||
for {
|
|
||||||
_ <- accumulator
|
|
||||||
_ <- resolveComponentGroups(pkg)
|
|
||||||
} yield ()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private def resolveComponentGroups(
|
|
||||||
pkg: Package[TruffleFile]
|
|
||||||
): Either[Error, Unit] =
|
|
||||||
if (loadedComponents.contains(pkg.libraryName)) Right(())
|
|
||||||
else {
|
|
||||||
pkg.getConfig().componentGroups match {
|
|
||||||
case Left(err) =>
|
|
||||||
Left(Error.PackageLoadingError(err.getMessage()))
|
|
||||||
case Right(componentGroups) =>
|
|
||||||
logger.debug(
|
|
||||||
s"Resolving component groups of package [${pkg.name}]."
|
|
||||||
)
|
|
||||||
|
|
||||||
registerComponentGroups(pkg.libraryName, componentGroups.newGroups)
|
|
||||||
componentGroups.extendedGroups
|
|
||||||
.foldLeft[Either[Error, Unit]](Right(())) {
|
|
||||||
(accumulator, componentGroup) =>
|
|
||||||
for {
|
|
||||||
_ <- accumulator
|
|
||||||
extendedLibraryName = componentGroup.group.libraryName
|
|
||||||
_ <- ensurePackageIsLoaded(extendedLibraryName)
|
|
||||||
pkgOpt = loadedPackages(extendedLibraryName)
|
|
||||||
_ <- pkgOpt.fold[Either[Error, Unit]](Right(()))(
|
|
||||||
resolveComponentGroups
|
|
||||||
)
|
|
||||||
_ = registerExtendedComponentGroup(
|
|
||||||
pkg.libraryName,
|
|
||||||
componentGroup
|
|
||||||
)
|
|
||||||
} yield ()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Register the list of component groups defined by a library.
|
|
||||||
*
|
|
||||||
* @param library the library name
|
|
||||||
* @param newGroups the list of component groups that the library defines
|
|
||||||
*/
|
|
||||||
private def registerComponentGroups(
|
|
||||||
library: LibraryName,
|
|
||||||
newGroups: List[ComponentGroup]
|
|
||||||
): Unit =
|
|
||||||
loadedComponents.updateWith(library) {
|
|
||||||
case Some(groups) =>
|
|
||||||
Some(groups.copy(newGroups = groups.newGroups ::: newGroups))
|
|
||||||
case None =>
|
|
||||||
Some(ComponentGroups(newGroups, List()))
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Register a component group extended by a library.
|
|
||||||
*
|
|
||||||
* @param library the library name
|
|
||||||
* @param group the extended component group
|
|
||||||
*/
|
|
||||||
private def registerExtendedComponentGroup(
|
|
||||||
library: LibraryName,
|
|
||||||
group: ExtendedComponentGroup
|
|
||||||
): Unit =
|
|
||||||
loadedComponents.updateWith(library) {
|
|
||||||
case Some(groups) =>
|
|
||||||
Some(groups.copy(extendedGroups = groups.extendedGroups :+ group))
|
|
||||||
case None =>
|
|
||||||
Some(ComponentGroups(List(), List(group)))
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @inheritdoc */
|
|
||||||
override def ensurePackageIsLoaded(
|
|
||||||
libraryName: LibraryName
|
|
||||||
): Either[Error, Unit] =
|
|
||||||
if (loadedPackages.contains(libraryName)) Right(())
|
|
||||||
else {
|
|
||||||
logger.trace(s"Resolving library $libraryName.")
|
|
||||||
val resolvedLibrary =
|
|
||||||
libraryProvider
|
|
||||||
.findLibrary(libraryName)
|
|
||||||
.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."
|
|
||||||
)
|
|
||||||
}
|
|
||||||
resolvedLibrary match {
|
|
||||||
case Left(error) =>
|
|
||||||
logger.warn(s"Resolution failed with [$error].", error)
|
|
||||||
case Right(resolved) =>
|
|
||||||
logger.info(
|
|
||||||
s"Found library ${resolved.name} @ ${resolved.version} " +
|
|
||||||
s"at [${MaskedPath(resolved.root.location).applyMasking()}]."
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
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.root)
|
|
||||||
}
|
|
||||||
.flatMap(resolveComponentGroups)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @inheritdoc */
|
|
||||||
def isPackageLoaded(libraryName: LibraryName): Boolean = {
|
|
||||||
loadedPackages.keySet.contains(libraryName)
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @inheritdoc */
|
|
||||||
override def getLoadedModules: Seq[Module] =
|
|
||||||
loadedModules.values.toSeq
|
|
||||||
|
|
||||||
/** @inheritdoc */
|
|
||||||
override def getLoadedPackages: Seq[Package[TruffleFile]] =
|
|
||||||
loadedPackages.values.toSeq.flatten
|
|
||||||
|
|
||||||
override def getLoadedPackagesJava
|
|
||||||
: java.lang.Iterable[Package[TruffleFile]] =
|
|
||||||
loadedPackages.flatMap(_._2).asJava
|
|
||||||
|
|
||||||
/** @inheritdoc */
|
|
||||||
override def getLoadedModule(qualifiedName: String): Option[Module] =
|
|
||||||
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 registerSyntheticPackage(
|
|
||||||
namespace: String,
|
|
||||||
name: String
|
|
||||||
): Unit =
|
|
||||||
loadedPackages.put(LibraryName(namespace, name), None)
|
|
||||||
|
|
||||||
/** Registering synthetic module, unlike the non-compiler generated one, is conditional
|
|
||||||
* in a sense that if a module already exists with a given name we only update its
|
|
||||||
* list of synthetic modules that it should export.
|
|
||||||
* If no module exists under the given name, we register the synthetic one.
|
|
||||||
*
|
|
||||||
* @param syntheticModule a synthetic module to register
|
|
||||||
* @param refs list of names of modules that should be exported by the module under the given name
|
|
||||||
*/
|
|
||||||
private def registerSyntheticModule(
|
|
||||||
syntheticModule: Module,
|
|
||||||
refs: List[QualifiedName]
|
|
||||||
): Unit = {
|
|
||||||
assert(syntheticModule.isSynthetic)
|
|
||||||
if (!loadedModules.contains(syntheticModule.getName.toString)) {
|
|
||||||
loadedModules.put(syntheticModule.getName.toString, syntheticModule)
|
|
||||||
} else {
|
|
||||||
val loaded = loadedModules(syntheticModule.getName.toString)
|
|
||||||
assert(!loaded.isSynthetic)
|
|
||||||
loaded.setDirectModulesRefs(refs.asJava)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override def deregisterModule(qualifiedName: String): Unit =
|
|
||||||
loadedModules.remove(qualifiedName)
|
|
||||||
|
|
||||||
/** @inheritdoc */
|
|
||||||
override def renameProject(
|
|
||||||
namespace: String,
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override def isNamespaceRegistered(namespace: String): Boolean =
|
|
||||||
loadedPackages.keySet.exists(_.namespace == namespace)
|
|
||||||
|
|
||||||
override def getPackageForLibrary(
|
|
||||||
libraryName: LibraryName
|
|
||||||
): Option[Package[TruffleFile]] =
|
|
||||||
loadedPackages.get(libraryName).flatten
|
|
||||||
|
|
||||||
override def getModulesForLibrary(libraryName: LibraryName): List[Module] =
|
|
||||||
getPackageForLibrary(libraryName)
|
|
||||||
.map(pkg => loadedModules.values.filter(_.getPackage == pkg).toList)
|
|
||||||
.getOrElse(Nil)
|
|
||||||
|
|
||||||
override def getLibraryBindings(
|
|
||||||
libraryName: LibraryName,
|
|
||||||
serializationManager: SerializationManager
|
|
||||||
): Option[ImportExportCache.CachedBindings] = {
|
|
||||||
ensurePackageIsLoaded(libraryName).toOption.flatMap { _ =>
|
|
||||||
if (!loadedLibraryBindings.contains(libraryName)) {
|
|
||||||
loadedPackages.get(libraryName).flatten.foreach(loadDependencies(_))
|
|
||||||
serializationManager
|
|
||||||
.deserializeLibraryBindings(libraryName)
|
|
||||||
.foreach(cache =>
|
|
||||||
loadedLibraryBindings.addOne((libraryName, cache))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
loadedLibraryBindings.get(libraryName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private def loadDependencies(pkg: Package[TruffleFile]): Unit = {
|
|
||||||
val manifestFile = fs.getChild(pkg.root, LibraryManifest.filename)
|
|
||||||
readManifest(manifestFile)
|
|
||||||
.flatMap(LibraryManifest.fromYaml(_))
|
|
||||||
.foreach(
|
|
||||||
_.dependencies.foreach(ensurePackageIsLoaded)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private def readManifest(file: TruffleFile): Try[String] = {
|
|
||||||
if (file.exists())
|
|
||||||
Using(file.newBufferedReader) { reader =>
|
|
||||||
StringUtils.join(reader.lines().iterator(), "\n")
|
|
||||||
}
|
|
||||||
else Failure(PackageManager.PackageNotFound())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Creates a [[PackageRepository]] for the run.
|
|
||||||
*
|
|
||||||
* It tries to load and resolve the edition used in the project (or the
|
|
||||||
* default edition), so that any libraries to be loaded can be resolved using
|
|
||||||
* that edition.
|
|
||||||
*
|
|
||||||
* Edition and library search paths are based on the distribution and
|
|
||||||
* language home (if it is provided).
|
|
||||||
*
|
|
||||||
* @param projectPackage the package of the current project (if ran inside of a project)
|
|
||||||
* @param languageHome the language home (if set)
|
|
||||||
* @param distributionManager the distribution manager
|
|
||||||
* @param resourceManager the resource manager instance
|
|
||||||
* @param context the context reference, needed to add polyglot libraries to
|
|
||||||
* the classpath
|
|
||||||
* @param builtins the builtins that are always preloaded
|
|
||||||
* @param notificationHandler a handler for library addition and progress
|
|
||||||
* notifications
|
|
||||||
* @return an initialized [[PackageRepository]]
|
|
||||||
*/
|
|
||||||
def initializeRepository(
|
|
||||||
projectPackage: Option[Package[TruffleFile]],
|
|
||||||
languageHome: Option[String],
|
|
||||||
editionOverride: Option[String],
|
|
||||||
distributionManager: DistributionManager,
|
|
||||||
resourceManager: ResourceManager,
|
|
||||||
context: EnsoContext,
|
|
||||||
builtins: Builtins,
|
|
||||||
notificationHandler: NotificationHandler
|
|
||||||
): PackageRepository = {
|
|
||||||
val rawEdition = editionOverride
|
|
||||||
.map(v => Editions.Raw.Edition(parent = Some(v)))
|
|
||||||
.orElse(
|
|
||||||
projectPackage
|
|
||||||
.flatMap(_.getConfig().edition)
|
|
||||||
)
|
|
||||||
.getOrElse(DefaultEdition.getDefaultEdition)
|
|
||||||
|
|
||||||
val homeManager = languageHome.map { home => LanguageHome(Path.of(home)) }
|
|
||||||
val editionManager = EditionManager(distributionManager, homeManager)
|
|
||||||
val edition = editionManager.resolveEdition(rawEdition).get
|
|
||||||
|
|
||||||
val resolvingLibraryProvider =
|
|
||||||
DefaultLibraryProvider.make(
|
|
||||||
distributionManager = distributionManager,
|
|
||||||
resourceManager = resourceManager,
|
|
||||||
lockUserInterface = notificationHandler,
|
|
||||||
progressReporter = notificationHandler,
|
|
||||||
languageHome = homeManager,
|
|
||||||
edition = edition,
|
|
||||||
preferLocalLibraries =
|
|
||||||
projectPackage.exists(_.getConfig().preferLocalLibraries)
|
|
||||||
)
|
|
||||||
new Default(
|
|
||||||
resolvingLibraryProvider,
|
|
||||||
context,
|
|
||||||
builtins,
|
|
||||||
notificationHandler
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,650 @@
|
|||||||
|
package org.enso.interpreter.runtime
|
||||||
|
|
||||||
|
import org.enso.compiler.PackageRepository
|
||||||
|
import org.enso.compiler.ImportExportCache
|
||||||
|
import org.enso.compiler.SerializationManager
|
||||||
|
import com.oracle.truffle.api.TruffleFile
|
||||||
|
import com.typesafe.scalalogging.Logger
|
||||||
|
import org.apache.commons.lang3.StringUtils
|
||||||
|
import org.enso.editions.LibraryVersion
|
||||||
|
import org.enso.interpreter.runtime.util.TruffleFileSystem
|
||||||
|
import org.enso.librarymanager.published.repository.LibraryManifest
|
||||||
|
import org.enso.librarymanager.resolved.LibraryRoot
|
||||||
|
import org.enso.librarymanager.{ResolvingLibraryProvider}
|
||||||
|
import org.enso.logger.masking.MaskedPath
|
||||||
|
import org.enso.pkg.{
|
||||||
|
Component,
|
||||||
|
ComponentGroup,
|
||||||
|
ExtendedComponentGroup,
|
||||||
|
PackageManager,
|
||||||
|
QualifiedName,
|
||||||
|
SourceFile
|
||||||
|
}
|
||||||
|
import org.enso.text.buffer.Rope
|
||||||
|
import org.enso.polyglot.CompilationStage
|
||||||
|
|
||||||
|
import java.nio.file.Path
|
||||||
|
import scala.collection.immutable.ListSet
|
||||||
|
import scala.jdk.CollectionConverters.{IterableHasAsJava, SeqHasAsJava}
|
||||||
|
import scala.util.{Failure, Try, Using}
|
||||||
|
import org.enso.distribution.locking.ResourceManager
|
||||||
|
import org.enso.distribution.{DistributionManager, LanguageHome}
|
||||||
|
import org.enso.editions.updater.EditionManager
|
||||||
|
import org.enso.editions.{DefaultEdition, Editions, LibraryName}
|
||||||
|
import org.enso.interpreter.instrument.NotificationHandler
|
||||||
|
import org.enso.interpreter.runtime.builtin.Builtins
|
||||||
|
import org.enso.interpreter.runtime.{EnsoContext, Module}
|
||||||
|
import org.enso.librarymanager.{DefaultLibraryProvider}
|
||||||
|
import org.enso.pkg.{ComponentGroups, Package}
|
||||||
|
|
||||||
|
/** 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
|
||||||
|
*/
|
||||||
|
private class DefaultPackageRepository(
|
||||||
|
libraryProvider: ResolvingLibraryProvider,
|
||||||
|
context: EnsoContext,
|
||||||
|
builtins: Builtins,
|
||||||
|
notificationHandler: NotificationHandler
|
||||||
|
) extends PackageRepository {
|
||||||
|
|
||||||
|
private val logger = Logger[DefaultPackageRepository]
|
||||||
|
|
||||||
|
implicit private val fs: TruffleFileSystem = new TruffleFileSystem
|
||||||
|
private val packageManager = new PackageManager[TruffleFile]
|
||||||
|
private var projectPackage: Option[Package[TruffleFile]] = None
|
||||||
|
|
||||||
|
/** 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.
|
||||||
|
*/
|
||||||
|
private val loadedPackages
|
||||||
|
: collection.mutable.Map[LibraryName, Option[Package[TruffleFile]]] = {
|
||||||
|
val builtinsName = LibraryName(Builtins.NAMESPACE, Builtins.PACKAGE_NAME)
|
||||||
|
collection.mutable.LinkedHashMap(builtinsName -> None)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The mapping containing loaded modules.
|
||||||
|
*
|
||||||
|
* We use [[String]] as the key as we often index into this map based on
|
||||||
|
* qualified names that come from interop (via
|
||||||
|
* [[org.enso.interpreter.runtime.scope.TopLevelScope]]). These arrive as
|
||||||
|
* Strings, and constantly converting them into [[QualifiedName]]s would
|
||||||
|
* add more overhead than is probably necessary.
|
||||||
|
*/
|
||||||
|
private val loadedModules: collection.concurrent.Map[String, Module] =
|
||||||
|
collection.concurrent.TrieMap(Builtins.MODULE_NAME -> builtins.getModule)
|
||||||
|
|
||||||
|
/** The mapping containing loaded component groups.
|
||||||
|
*
|
||||||
|
* It should be modified and read only from within synchronized sections.
|
||||||
|
* The component mapping is added to the collection after ensuring that the
|
||||||
|
* corresponding library was loaded.
|
||||||
|
*/
|
||||||
|
private val loadedComponents
|
||||||
|
: collection.mutable.Map[LibraryName, ComponentGroups] = {
|
||||||
|
val builtinsName = LibraryName(Builtins.NAMESPACE, Builtins.PACKAGE_NAME)
|
||||||
|
collection.mutable.LinkedHashMap(builtinsName -> ComponentGroups.empty)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The mapping between the library and its cached bindings, if already laoded. */
|
||||||
|
private val loadedLibraryBindings: collection.mutable.Map[
|
||||||
|
LibraryName,
|
||||||
|
ImportExportCache.CachedBindings
|
||||||
|
] =
|
||||||
|
collection.mutable.LinkedHashMap()
|
||||||
|
|
||||||
|
private def getComponentModules: ListSet[Module] = {
|
||||||
|
val modules = for {
|
||||||
|
componentGroups <- loadedComponents.values
|
||||||
|
newComponents = componentGroups.newGroups.flatMap(_.exports)
|
||||||
|
extendedComponents = componentGroups.extendedGroups.flatMap(_.exports)
|
||||||
|
component <- newComponents ++ extendedComponents
|
||||||
|
module <- findComponentModule(component)
|
||||||
|
} yield module
|
||||||
|
modules.to(ListSet)
|
||||||
|
}
|
||||||
|
|
||||||
|
private def findComponentModule(component: Component): Option[Module] = {
|
||||||
|
def mkModuleName(path: Array[String]): String =
|
||||||
|
path.mkString(LibraryName.separator.toString)
|
||||||
|
@scala.annotation.tailrec
|
||||||
|
def go(path: Array[String]): Option[Module] =
|
||||||
|
if (path.isEmpty) None
|
||||||
|
else {
|
||||||
|
loadedModules.get(mkModuleName(path)) match {
|
||||||
|
case Some(module) => Some(module)
|
||||||
|
case None => go(path.init)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
go(component.name.split(LibraryName.separator))
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @inheritdoc */
|
||||||
|
override def getModuleMap: PackageRepository.ModuleMap = loadedModules
|
||||||
|
|
||||||
|
/** @inheritdoc */
|
||||||
|
override def freezeModuleMap: PackageRepository.FrozenModuleMap =
|
||||||
|
loadedModules.toMap
|
||||||
|
|
||||||
|
/** @inheritdoc */
|
||||||
|
override def getComponents: PackageRepository.ComponentsMap =
|
||||||
|
this.synchronized {
|
||||||
|
loadedComponents.toMap
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @inheritdoc */
|
||||||
|
override def getPendingModules: ListSet[Module] =
|
||||||
|
this.synchronized {
|
||||||
|
for {
|
||||||
|
module <- getComponentModules
|
||||||
|
isCompiled =
|
||||||
|
module.getCompilationStage.isAtLeast(
|
||||||
|
CompilationStage.AFTER_CODEGEN
|
||||||
|
)
|
||||||
|
if !isCompiled
|
||||||
|
} yield module
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @inheritdoc */
|
||||||
|
override def registerMainProjectPackage(
|
||||||
|
libraryName: LibraryName,
|
||||||
|
pkg: Package[TruffleFile]
|
||||||
|
): Unit = {
|
||||||
|
projectPackage = Some(pkg)
|
||||||
|
registerPackageInternal(
|
||||||
|
libraryName = libraryName,
|
||||||
|
pkg = pkg,
|
||||||
|
libraryVersion = LibraryVersion.Local,
|
||||||
|
isLibrary = false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @inheritdoc */
|
||||||
|
override def getMainProjectPackage: Option[Package[TruffleFile]] = {
|
||||||
|
projectPackage
|
||||||
|
}
|
||||||
|
|
||||||
|
private def registerPackageInternal(
|
||||||
|
libraryName: LibraryName,
|
||||||
|
libraryVersion: LibraryVersion,
|
||||||
|
pkg: Package[TruffleFile],
|
||||||
|
isLibrary: Boolean
|
||||||
|
): Unit = {
|
||||||
|
val extensions = pkg.listPolyglotExtensions("java")
|
||||||
|
extensions.foreach(context.getEnvironment.addToHostClassPath)
|
||||||
|
|
||||||
|
val (regularModules, syntheticModulesMetadata) = pkg
|
||||||
|
.listSources()
|
||||||
|
.map(srcFile =>
|
||||||
|
(
|
||||||
|
new Module(srcFile.qualifiedName, pkg, srcFile.file),
|
||||||
|
inferSyntheticModules(srcFile)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.unzip
|
||||||
|
|
||||||
|
regularModules.foreach(registerModule)
|
||||||
|
|
||||||
|
syntheticModulesMetadata.flatten
|
||||||
|
.groupMap(_._1)(v => (v._2, v._3))
|
||||||
|
.foreach { case (qName, modulesWithSources) =>
|
||||||
|
val source = modulesWithSources
|
||||||
|
.map(_._2)
|
||||||
|
.foldLeft("")(_ ++ "\n" ++ _)
|
||||||
|
registerSyntheticModule(
|
||||||
|
Module.synthetic(
|
||||||
|
qName,
|
||||||
|
pkg,
|
||||||
|
Rope(source)
|
||||||
|
),
|
||||||
|
modulesWithSources.map(_._1)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isLibrary) {
|
||||||
|
val root = Path.of(pkg.root.toString)
|
||||||
|
notificationHandler.addedLibrary(libraryName, libraryVersion, root)
|
||||||
|
}
|
||||||
|
|
||||||
|
loadedPackages.put(libraryName, Some(pkg))
|
||||||
|
}
|
||||||
|
|
||||||
|
/** For any given source file, infer data necessary to generate synthetic modules as well as their contents.
|
||||||
|
* E.g., for A/B/C.enso it infers modules
|
||||||
|
* - A.B that exports A.B.C
|
||||||
|
* - A that exports A.B
|
||||||
|
*
|
||||||
|
* @param srcFile Enso source file to consider
|
||||||
|
* @return a list of triples representing the name of submodule along the path, what submodule it exports and its contents
|
||||||
|
*/
|
||||||
|
private def inferSyntheticModules(
|
||||||
|
srcFile: SourceFile[TruffleFile]
|
||||||
|
): List[(QualifiedName, QualifiedName, String)] = {
|
||||||
|
def listAllIntermediateModules(
|
||||||
|
namespace: String,
|
||||||
|
name: String,
|
||||||
|
elements: List[String],
|
||||||
|
exportItem: String
|
||||||
|
): List[(QualifiedName, QualifiedName, String)] = {
|
||||||
|
elements match {
|
||||||
|
case Nil =>
|
||||||
|
Nil
|
||||||
|
case lastModuleName :: parts =>
|
||||||
|
val pathElems = elements.reverse
|
||||||
|
val modName =
|
||||||
|
s"${namespace}.$name.${pathElems.mkString(".")}.$exportItem"
|
||||||
|
val modSource =
|
||||||
|
s"""|import $modName
|
||||||
|
|export $modName
|
||||||
|
|""".stripMargin
|
||||||
|
val syntheticModuleInfo = (
|
||||||
|
QualifiedName(namespace :: name :: parts.reverse, lastModuleName),
|
||||||
|
QualifiedName(namespace :: name :: pathElems, exportItem),
|
||||||
|
modSource
|
||||||
|
)
|
||||||
|
syntheticModuleInfo :: listAllIntermediateModules(
|
||||||
|
namespace,
|
||||||
|
name,
|
||||||
|
parts,
|
||||||
|
lastModuleName
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
srcFile.qualifiedName.path match {
|
||||||
|
case namespace :: name :: rest =>
|
||||||
|
listAllIntermediateModules(
|
||||||
|
namespace,
|
||||||
|
name,
|
||||||
|
rest.reverse,
|
||||||
|
srcFile.qualifiedName.item
|
||||||
|
)
|
||||||
|
case _ =>
|
||||||
|
Nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** This package modifies the [[loadedPackages]], so it should be only
|
||||||
|
* called from within synchronized sections.
|
||||||
|
*/
|
||||||
|
private def loadPackage(
|
||||||
|
libraryName: LibraryName,
|
||||||
|
libraryVersion: LibraryVersion,
|
||||||
|
root: LibraryRoot
|
||||||
|
): Either[PackageRepository.Error, Package[TruffleFile]] = Try {
|
||||||
|
logger.debug(
|
||||||
|
s"Loading library $libraryName from " +
|
||||||
|
s"[${MaskedPath(root.location).applyMasking()}]."
|
||||||
|
)
|
||||||
|
val rootFile = context.getEnvironment.getInternalTruffleFile(
|
||||||
|
root.location.toAbsolutePath.normalize.toString
|
||||||
|
)
|
||||||
|
val pkg = packageManager.loadPackage(rootFile).get
|
||||||
|
registerPackageInternal(
|
||||||
|
libraryName = libraryName,
|
||||||
|
libraryVersion = libraryVersion,
|
||||||
|
pkg = pkg,
|
||||||
|
isLibrary = true
|
||||||
|
)
|
||||||
|
pkg
|
||||||
|
}.toEither.left.map { error =>
|
||||||
|
PackageRepository.Error.PackageLoadingError(error.getMessage)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @inheritdoc */
|
||||||
|
override def initialize(): Either[PackageRepository.Error, Unit] =
|
||||||
|
this.synchronized {
|
||||||
|
val unprocessedPackages =
|
||||||
|
loadedPackages.keySet
|
||||||
|
.diff(loadedComponents.keySet)
|
||||||
|
.flatMap(loadedPackages(_))
|
||||||
|
unprocessedPackages.foldLeft[Either[PackageRepository.Error, Unit]](
|
||||||
|
Right(())
|
||||||
|
) { (accumulator, pkg) =>
|
||||||
|
for {
|
||||||
|
_ <- accumulator
|
||||||
|
_ <- resolveComponentGroups(pkg)
|
||||||
|
} yield ()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private def resolveComponentGroups(
|
||||||
|
pkg: Package[TruffleFile]
|
||||||
|
): Either[PackageRepository.Error, Unit] =
|
||||||
|
if (loadedComponents.contains(pkg.libraryName)) Right(())
|
||||||
|
else {
|
||||||
|
pkg.getConfig().componentGroups match {
|
||||||
|
case Left(err) =>
|
||||||
|
Left(PackageRepository.Error.PackageLoadingError(err.getMessage()))
|
||||||
|
case Right(componentGroups) =>
|
||||||
|
logger.debug(
|
||||||
|
s"Resolving component groups of package [${pkg.name}]."
|
||||||
|
)
|
||||||
|
|
||||||
|
registerComponentGroups(pkg.libraryName, componentGroups.newGroups)
|
||||||
|
componentGroups.extendedGroups
|
||||||
|
.foldLeft[Either[PackageRepository.Error, Unit]](Right(())) {
|
||||||
|
(accumulator, componentGroup) =>
|
||||||
|
for {
|
||||||
|
_ <- accumulator
|
||||||
|
extendedLibraryName = componentGroup.group.libraryName
|
||||||
|
_ <- ensurePackageIsLoaded(extendedLibraryName)
|
||||||
|
pkgOpt = loadedPackages(extendedLibraryName)
|
||||||
|
_ <- pkgOpt.fold[Either[PackageRepository.Error, Unit]](
|
||||||
|
Right(())
|
||||||
|
)(
|
||||||
|
resolveComponentGroups
|
||||||
|
)
|
||||||
|
_ = registerExtendedComponentGroup(
|
||||||
|
pkg.libraryName,
|
||||||
|
componentGroup
|
||||||
|
)
|
||||||
|
} yield ()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Register the list of component groups defined by a library.
|
||||||
|
*
|
||||||
|
* @param library the library name
|
||||||
|
* @param newGroups the list of component groups that the library defines
|
||||||
|
*/
|
||||||
|
private def registerComponentGroups(
|
||||||
|
library: LibraryName,
|
||||||
|
newGroups: List[ComponentGroup]
|
||||||
|
): Unit =
|
||||||
|
loadedComponents.updateWith(library) {
|
||||||
|
case Some(groups) =>
|
||||||
|
Some(groups.copy(newGroups = groups.newGroups ::: newGroups))
|
||||||
|
case None =>
|
||||||
|
Some(ComponentGroups(newGroups, List()))
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Register a component group extended by a library.
|
||||||
|
*
|
||||||
|
* @param library the library name
|
||||||
|
* @param group the extended component group
|
||||||
|
*/
|
||||||
|
private def registerExtendedComponentGroup(
|
||||||
|
library: LibraryName,
|
||||||
|
group: ExtendedComponentGroup
|
||||||
|
): Unit =
|
||||||
|
loadedComponents.updateWith(library) {
|
||||||
|
case Some(groups) =>
|
||||||
|
Some(groups.copy(extendedGroups = groups.extendedGroups :+ group))
|
||||||
|
case None =>
|
||||||
|
Some(ComponentGroups(List(), List(group)))
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @inheritdoc */
|
||||||
|
override def ensurePackageIsLoaded(
|
||||||
|
libraryName: LibraryName
|
||||||
|
): Either[PackageRepository.Error, Unit] =
|
||||||
|
if (loadedPackages.contains(libraryName)) Right(())
|
||||||
|
else {
|
||||||
|
logger.trace(s"Resolving library $libraryName.")
|
||||||
|
val resolvedLibrary =
|
||||||
|
libraryProvider
|
||||||
|
.findLibrary(libraryName)
|
||||||
|
.left
|
||||||
|
.map {
|
||||||
|
case ResolvingLibraryProvider.Error.NotResolved(details) =>
|
||||||
|
PackageRepository.Error.PackageCouldNotBeResolved(details)
|
||||||
|
case ResolvingLibraryProvider.Error.DownloadFailed(_, reason) =>
|
||||||
|
PackageRepository.Error.PackageDownloadFailed(reason)
|
||||||
|
case ResolvingLibraryProvider.Error.RequestedLocalLibraryDoesNotExist =>
|
||||||
|
PackageRepository.Error.PackageLoadingError(
|
||||||
|
"The local library has not been found on the local " +
|
||||||
|
"libraries search paths."
|
||||||
|
)
|
||||||
|
}
|
||||||
|
resolvedLibrary match {
|
||||||
|
case Left(error) =>
|
||||||
|
logger.warn(s"Resolution failed with [$error].", error)
|
||||||
|
case Right(resolved) =>
|
||||||
|
logger.info(
|
||||||
|
s"Found library ${resolved.name} @ ${resolved.version} " +
|
||||||
|
s"at [${MaskedPath(resolved.root.location).applyMasking()}]."
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
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.root)
|
||||||
|
}
|
||||||
|
.flatMap(resolveComponentGroups)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @inheritdoc */
|
||||||
|
def isPackageLoaded(libraryName: LibraryName): Boolean = {
|
||||||
|
loadedPackages.keySet.contains(libraryName)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @inheritdoc */
|
||||||
|
override def getLoadedModules: Seq[Module] =
|
||||||
|
loadedModules.values.toSeq
|
||||||
|
|
||||||
|
/** @inheritdoc */
|
||||||
|
override def getLoadedPackages: Seq[Package[TruffleFile]] =
|
||||||
|
loadedPackages.values.toSeq.flatten
|
||||||
|
|
||||||
|
override def getLoadedPackagesJava: java.lang.Iterable[Package[TruffleFile]] =
|
||||||
|
loadedPackages.flatMap(_._2).asJava
|
||||||
|
|
||||||
|
/** @inheritdoc */
|
||||||
|
override def getLoadedModule(qualifiedName: String): Option[Module] =
|
||||||
|
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 registerSyntheticPackage(
|
||||||
|
namespace: String,
|
||||||
|
name: String
|
||||||
|
): Unit =
|
||||||
|
loadedPackages.put(LibraryName(namespace, name), None)
|
||||||
|
|
||||||
|
/** Registering synthetic module, unlike the non-compiler generated one, is conditional
|
||||||
|
* in a sense that if a module already exists with a given name we only update its
|
||||||
|
* list of synthetic modules that it should export.
|
||||||
|
* If no module exists under the given name, we register the synthetic one.
|
||||||
|
*
|
||||||
|
* @param syntheticModule a synthetic module to register
|
||||||
|
* @param refs list of names of modules that should be exported by the module under the given name
|
||||||
|
*/
|
||||||
|
private def registerSyntheticModule(
|
||||||
|
syntheticModule: Module,
|
||||||
|
refs: List[QualifiedName]
|
||||||
|
): Unit = {
|
||||||
|
assert(syntheticModule.isSynthetic)
|
||||||
|
if (!loadedModules.contains(syntheticModule.getName.toString)) {
|
||||||
|
loadedModules.put(syntheticModule.getName.toString, syntheticModule)
|
||||||
|
} else {
|
||||||
|
val loaded = loadedModules(syntheticModule.getName.toString)
|
||||||
|
assert(!loaded.isSynthetic)
|
||||||
|
loaded.setDirectModulesRefs(refs.asJava)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override def deregisterModule(qualifiedName: String): Unit =
|
||||||
|
loadedModules.remove(qualifiedName)
|
||||||
|
|
||||||
|
/** @inheritdoc */
|
||||||
|
override def renameProject(
|
||||||
|
namespace: String,
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override def isNamespaceRegistered(namespace: String): Boolean =
|
||||||
|
loadedPackages.keySet.exists(_.namespace == namespace)
|
||||||
|
|
||||||
|
override def getPackageForLibrary(
|
||||||
|
libraryName: LibraryName
|
||||||
|
): Option[Package[TruffleFile]] =
|
||||||
|
loadedPackages.get(libraryName).flatten
|
||||||
|
|
||||||
|
override def getModulesForLibrary(libraryName: LibraryName): List[Module] =
|
||||||
|
getPackageForLibrary(libraryName)
|
||||||
|
.map(pkg => loadedModules.values.filter(_.getPackage == pkg).toList)
|
||||||
|
.getOrElse(Nil)
|
||||||
|
|
||||||
|
override def getLibraryBindings(
|
||||||
|
libraryName: LibraryName,
|
||||||
|
serializationManager: SerializationManager
|
||||||
|
): Option[ImportExportCache.CachedBindings] = {
|
||||||
|
ensurePackageIsLoaded(libraryName).toOption.flatMap { _ =>
|
||||||
|
if (!loadedLibraryBindings.contains(libraryName)) {
|
||||||
|
loadedPackages.get(libraryName).flatten.foreach(loadDependencies(_))
|
||||||
|
serializationManager
|
||||||
|
.deserializeLibraryBindings(libraryName)
|
||||||
|
.foreach(cache => loadedLibraryBindings.addOne((libraryName, cache)))
|
||||||
|
}
|
||||||
|
loadedLibraryBindings.get(libraryName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private def loadDependencies(pkg: Package[TruffleFile]): Unit = {
|
||||||
|
val manifestFile = fs.getChild(pkg.root, LibraryManifest.filename)
|
||||||
|
readManifest(manifestFile)
|
||||||
|
.flatMap(LibraryManifest.fromYaml(_))
|
||||||
|
.foreach(
|
||||||
|
_.dependencies.foreach(ensurePackageIsLoaded)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private def readManifest(file: TruffleFile): Try[String] = {
|
||||||
|
if (file.exists())
|
||||||
|
Using(file.newBufferedReader) { reader =>
|
||||||
|
StringUtils.join(reader.lines().iterator(), "\n")
|
||||||
|
}
|
||||||
|
else Failure(PackageManager.PackageNotFound())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private object DefaultPackageRepository {
|
||||||
|
|
||||||
|
/** Creates a [[PackageRepository]] for the run.
|
||||||
|
*
|
||||||
|
* It tries to load and resolve the edition used in the project (or the
|
||||||
|
* default edition), so that any libraries to be loaded can be resolved using
|
||||||
|
* that edition.
|
||||||
|
*
|
||||||
|
* Edition and library search paths are based on the distribution and
|
||||||
|
* language home (if it is provided).
|
||||||
|
*
|
||||||
|
* @param projectPackage the package of the current project (if ran inside of a project)
|
||||||
|
* @param languageHome the language home (if set)
|
||||||
|
* @param distributionManager the distribution manager
|
||||||
|
* @param resourceManager the resource manager instance
|
||||||
|
* @param context the context reference, needed to add polyglot libraries to
|
||||||
|
* the classpath
|
||||||
|
* @param builtins the builtins that are always preloaded
|
||||||
|
* @param notificationHandler a handler for library addition and progress
|
||||||
|
* notifications
|
||||||
|
* @return an initialized [[PackageRepository]]
|
||||||
|
*/
|
||||||
|
def initializeRepository(
|
||||||
|
projectPackage: Option[Package[TruffleFile]],
|
||||||
|
languageHome: Option[String],
|
||||||
|
editionOverride: Option[String],
|
||||||
|
distributionManager: DistributionManager,
|
||||||
|
resourceManager: ResourceManager,
|
||||||
|
context: EnsoContext,
|
||||||
|
builtins: Builtins,
|
||||||
|
notificationHandler: NotificationHandler
|
||||||
|
): PackageRepository = {
|
||||||
|
val rawEdition = editionOverride
|
||||||
|
.map(v => Editions.Raw.Edition(parent = Some(v)))
|
||||||
|
.orElse(
|
||||||
|
projectPackage
|
||||||
|
.flatMap(_.getConfig().edition)
|
||||||
|
)
|
||||||
|
.getOrElse(DefaultEdition.getDefaultEdition)
|
||||||
|
|
||||||
|
val homeManager = languageHome.map { home => LanguageHome(Path.of(home)) }
|
||||||
|
val editionManager = EditionManager(distributionManager, homeManager)
|
||||||
|
val edition = editionManager.resolveEdition(rawEdition).get
|
||||||
|
|
||||||
|
val resolvingLibraryProvider =
|
||||||
|
DefaultLibraryProvider.make(
|
||||||
|
distributionManager = distributionManager,
|
||||||
|
resourceManager = resourceManager,
|
||||||
|
lockUserInterface = notificationHandler,
|
||||||
|
progressReporter = notificationHandler,
|
||||||
|
languageHome = homeManager,
|
||||||
|
edition = edition,
|
||||||
|
preferLocalLibraries =
|
||||||
|
projectPackage.exists(_.getConfig().preferLocalLibraries)
|
||||||
|
)
|
||||||
|
new DefaultPackageRepository(
|
||||||
|
resolvingLibraryProvider,
|
||||||
|
context,
|
||||||
|
builtins,
|
||||||
|
notificationHandler
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user