From 0777b32610777819ae376aa7c83911f9a96e5119 Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Fri, 13 Dec 2024 17:39:17 +0100 Subject: [PATCH 1/4] Register services in package.yaml instead via ServiceLoader --- .../lib/Standard/Base/0.0.0-dev/package.yaml | 4 + .../src/Enso_Cloud/Enso_File_System_Impl.enso | 9 ++ .../Base/0.0.0-dev/src/System/File.enso | 22 +++-- .../test/interop/MetaServicesTest.java | 97 +++++++++++++++++++ .../node/callable/InteropApplicationNode.java | 9 ++ .../src/main/scala/org/enso/pkg/Config.scala | 59 ++++++++++- .../src/main/scala/org/enso/pkg/Package.scala | 6 +- 7 files changed, 195 insertions(+), 11 deletions(-) create mode 100644 distribution/lib/Standard/Base/0.0.0-dev/src/Enso_Cloud/Enso_File_System_Impl.enso create mode 100644 engine/runtime-integration-tests/src/test/java/org/enso/interpreter/test/interop/MetaServicesTest.java diff --git a/distribution/lib/Standard/Base/0.0.0-dev/package.yaml b/distribution/lib/Standard/Base/0.0.0-dev/package.yaml index 8c92eac4c74b..14647940afa5 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/package.yaml +++ b/distribution/lib/Standard/Base/0.0.0-dev/package.yaml @@ -29,3 +29,7 @@ component-groups: - Logical: { color: oklch(0.464 0.14 300) } - Operators: { color: oklch(0.464 0.14 275) } - Errors: { color: oklch(0.464 0.14 15) } + +services: + - provides: Standard.Base.System.File.File_System_SPI + with: Standard.Base.Enso_Cloud.Enso_File_System_Impl.Enso_File_System_Impl diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Enso_Cloud/Enso_File_System_Impl.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Enso_Cloud/Enso_File_System_Impl.enso new file mode 100644 index 000000000000..c538be479dfa --- /dev/null +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Enso_Cloud/Enso_File_System_Impl.enso @@ -0,0 +1,9 @@ +private + +import project.System.File.File_System_SPI +import project.Enso_Cloud.Enso_File + +type Enso_File_System_Impl + +File_System_SPI.from (_:Enso_File_System_Impl) = + File_System_SPI.new "enso" Enso_File diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/System/File.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/System/File.enso index 825069dfc9ad..be3944ec2fd8 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/System/File.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/System/File.enso @@ -47,11 +47,6 @@ polyglot java import java.nio.file.StandardOpenOption polyglot java import java.time.ZonedDateTime polyglot java import org.enso.base.DryRunFileManager polyglot java import org.enso.base.file_system.File_Utils -polyglot java import org.enso.base.file_system.FileSystemSPI - -## PRIVATE -file_types : Vector -file_types = Vector.from_polyglot_array (FileSystemSPI.get_types False) ## Represents a file or folder on the filesystem. @Builtin_Type @@ -81,13 +76,13 @@ type File new path = case path of _ : Text -> if path.contains "://" . not then resolve_path path else protocol = path.split "://" . first - file_system = FileSystemSPI.get_type protocol False + file_system = File_System_SPI.get_type protocol False if file_system.is_nothing then Error.throw (Illegal_Argument.Error "Unsupported protocol "+protocol) else file_system.new path _ : File -> path _ -> ## Check to see if a valid "File" type. - if (file_types.any file_type-> path.is_a file_type) then path else + if ((File_System_SPI.get_types False).any file_type-> path.is_a file_type) then path else Error.throw (Illegal_Argument.Error "The provided path is neither a Text, nor any recognized File-like type.") @@ -924,3 +919,16 @@ local_file_move (source : File) (destination : File) (replace_existing : Boolean File_Error.handle_java_exceptions source <| copy_options = if replace_existing then [StandardCopyOption.REPLACE_EXISTING.to_text] else [] source.move_builtin destination copy_options + +# PRIVATE +type File_System_SPI + private Entry protocol:Text typ + + new protocol:Text typ = File_System_SPI.Entry protocol typ + + private get_type protocol:Text _:Boolean = + vec = Self.get_types . filter (_.protocol == protocol) + if vec . not_empty then vec.first else Nothing + + private get_types _:Boolean = + Meta.lookup_services File_System_SPI diff --git a/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/test/interop/MetaServicesTest.java b/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/test/interop/MetaServicesTest.java new file mode 100644 index 000000000000..e7d8f5f0a1dd --- /dev/null +++ b/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/test/interop/MetaServicesTest.java @@ -0,0 +1,97 @@ +package org.enso.interpreter.test.interop; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.net.URI; +import org.enso.common.MethodNames; +import org.enso.interpreter.node.callable.InteropApplicationNode; +import org.enso.interpreter.runtime.callable.UnresolvedConversion; +import org.enso.interpreter.runtime.data.Type; +import org.enso.interpreter.runtime.state.State; +import org.enso.test.utils.ContextUtils; +import org.graalvm.polyglot.Context; +import org.graalvm.polyglot.Source; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; + +public class MetaServicesTest { + private static Context ctx; + + @BeforeClass + public static void prepareCtx() { + ctx = ContextUtils.createDefaultContext(); + } + + @AfterClass + public static void disposeCtx() { + ctx.close(); + ctx = null; + } + + @Test + public void loadFileSystemServices() throws Exception { + final URI uri = new URI("memory://services.enso"); + final Source src = + Source.newBuilder( + "enso", + """ + import Standard.Base.System.File.File_System_SPI + import Standard.Base.Meta + import Standard.Base.Enso_Cloud.Enso_File_System_Impl + + fs_spi = File_System_SPI + + data = + Meta.lookup_services File_System_SPI + """, + "services.enso") + .uri(uri) + .buildLiteral(); + + var module = ctx.eval(src); + var fileSystemSpi = module.invokeMember(MethodNames.Module.EVAL_EXPRESSION, "fs_spi"); + assertTrue("It is a type: " + fileSystemSpi, fileSystemSpi.isMetaObject()); + var fileSystemSpiRaw = (Type) ContextUtils.unwrapValue(ctx, fileSystemSpi); + var ensoCtx = ContextUtils.leakContext(ctx); + + for (var p : ensoCtx.getPackageRepository().getLoadedPackagesJava()) { + p.getConfig() + .services() + .foreach( + pw -> { + var m = + ensoCtx + .getTopScope() + .getModule("Standard.Base.Enso_Cloud.Enso_File_System_Impl") + .get(); + var all = m.getScope().getAllTypes(); + var fsImpl = + ContextUtils.executeInContext( + ctx, + () -> { + var conversion = UnresolvedConversion.build(m.getScope()); + var state = State.create(ensoCtx); + var node = InteropApplicationNode.getUncached(); + for (var t : all) { + var fn = conversion.resolveFor(ensoCtx, fileSystemSpiRaw, t); + var conv = node.execute(fn, state, new Object[] {fileSystemSpiRaw, t}); + if (conv != null) { + return conv; + } + } + return null; + }); + assertNotNull("Some implementation found", fsImpl); + assertEquals("Protocol", "enso", fsImpl.getMember("protocol").asString()); + assertEquals( + "Type", + "Standard.Base.Enso_Cloud.Enso_File", + fsImpl.getMember("typ").getMetaQualifiedName()); + return null; + }); + } + } +} diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/callable/InteropApplicationNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/callable/InteropApplicationNode.java index 0e3364a6899a..6610676fb216 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/callable/InteropApplicationNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/callable/InteropApplicationNode.java @@ -34,6 +34,15 @@ public static InteropApplicationNode build() { return InteropApplicationNodeGen.create(); } + /** + * Returns shared, uncached instance of this node + * + * @return shared interop application node + */ + public static InteropApplicationNode getUncached() { + return InteropApplicationNodeGen.getUncached(); + } + /** * Calls the function with given state and arguments. * diff --git a/lib/scala/pkg/src/main/scala/org/enso/pkg/Config.scala b/lib/scala/pkg/src/main/scala/org/enso/pkg/Config.scala index 3adb2ecfd257..39e8977a92b0 100644 --- a/lib/scala/pkg/src/main/scala/org/enso/pkg/Config.scala +++ b/lib/scala/pkg/src/main/scala/org/enso/pkg/Config.scala @@ -13,6 +13,53 @@ import org.yaml.snakeyaml.nodes.{MappingNode, Node} import java.io.{Reader, StringReader} import java.util import scala.util.Try +import java.io.IOException + +/** Information about registered service. + * + * @param provides name of SPI type + * @param with name of implementation type + */ +case class ProvidesWith(val provides: String, val `with`: String) {} + +object ProvidesWith { + + /** Fields for use when serializing the [[ProvidesWith]]. */ + object Fields { + val Provides = "provides" + val With = "with" + } + + implicit val decoderSnake: YamlDecoder[ProvidesWith] = + new YamlDecoder[ProvidesWith] { + override def decode(node: Node): Either[Throwable, ProvidesWith] = + node match { + case mappingNode: MappingNode => + val str = implicitly[YamlDecoder[String]] + val bindings = mappingKV(mappingNode) + for { + p <- bindings + .get(Fields.Provides) + .map(str.decode) + .getOrElse(Left(new IOException("Missing provides"))) + w <- bindings + .get(Fields.With) + .map(str.decode) + .getOrElse(Left(new IOException("Missing provides"))) + } yield ProvidesWith(p, w) + } + } + + implicit val encoderSnake: YamlEncoder[ProvidesWith] = + new YamlEncoder[ProvidesWith] { + override def encode(value: ProvidesWith) = { + val elements = new util.ArrayList[(String, Object)]() + elements.add((Fields.Provides, value.provides)) + elements.add((Fields.With, value.`with`)) + toMap(elements) + } + } +} /** Contact information to a user. * @@ -110,7 +157,8 @@ case class Config( maintainers: List[Contact], edition: Option[Editions.RawEdition], preferLocalLibraries: Boolean, - componentGroups: Option[ComponentGroups] + componentGroups: Option[ComponentGroups], + services: List[ProvidesWith] ) { /** Converts the configuration into a YAML representation. */ @@ -160,6 +208,7 @@ object Config { val Edition: String = "edition" val PreferLocalLibraries = "prefer-local-libraries" val ComponentGroups = "component-groups" + val Services: String = "services" } implicit val yamlDecoder: YamlDecoder[Config] = @@ -171,6 +220,7 @@ object Config { val normalizedNameDecoder = implicitly[YamlDecoder[Option[String]]] val contactDecoder = implicitly[YamlDecoder[List[Contact]]] + val servicesDecoder = implicitly[YamlDecoder[List[ProvidesWith]]] val editionNameDecoder = implicitly[YamlDecoder[EditionName]] val editionDecoder = implicitly[YamlDecoder[Option[Editions.RawEdition]]] @@ -233,6 +283,10 @@ object Config { .get(JsonFields.ComponentGroups) .map(componentGroups.decode) .getOrElse(Right(None)) + services <- clazzMap + .get(JsonFields.Services) + .map(servicesDecoder.decode) + .getOrElse(Right(Nil)) } yield Config( name, normalizedName, @@ -243,7 +297,8 @@ object Config { maintainers, edition, preferLocalLibraries, - componentGroups + componentGroups, + services ) } } diff --git a/lib/scala/pkg/src/main/scala/org/enso/pkg/Package.scala b/lib/scala/pkg/src/main/scala/org/enso/pkg/Package.scala index 32279c2764b0..32d8c4b14a1e 100644 --- a/lib/scala/pkg/src/main/scala/org/enso/pkg/Package.scala +++ b/lib/scala/pkg/src/main/scala/org/enso/pkg/Package.scala @@ -288,7 +288,8 @@ class PackageManager[F](implicit val fileSystem: FileSystem[F]) { authors: List[Contact] = List(), maintainers: List[Contact] = List(), license: String = "", - componentGroups: Option[ComponentGroups] = None + componentGroups: Option[ComponentGroups] = None, + services: List[ProvidesWith] = List() ): Package[F] = { val config = Config( name = name, @@ -300,7 +301,8 @@ class PackageManager[F](implicit val fileSystem: FileSystem[F]) { edition = edition, preferLocalLibraries = true, maintainers = maintainers, - componentGroups = componentGroups + componentGroups = componentGroups, + services = services ) create(root, config, template) } From 8e3b3e2252c310dcf4e0d0da64610126e7dbfa69 Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Fri, 13 Dec 2024 18:13:10 +0100 Subject: [PATCH 2/4] Really search for ProvidesWith types --- .../test/interop/MetaServicesTest.java | 39 +++++++++---------- 1 file changed, 18 insertions(+), 21 deletions(-) diff --git a/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/test/interop/MetaServicesTest.java b/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/test/interop/MetaServicesTest.java index e7d8f5f0a1dd..a56f6e97e0a0 100644 --- a/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/test/interop/MetaServicesTest.java +++ b/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/test/interop/MetaServicesTest.java @@ -2,11 +2,10 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; import java.net.URI; -import org.enso.common.MethodNames; import org.enso.interpreter.node.callable.InteropApplicationNode; +import org.enso.interpreter.runtime.EnsoContext; import org.enso.interpreter.runtime.callable.UnresolvedConversion; import org.enso.interpreter.runtime.data.Type; import org.enso.interpreter.runtime.state.State; @@ -42,8 +41,6 @@ public void loadFileSystemServices() throws Exception { import Standard.Base.Meta import Standard.Base.Enso_Cloud.Enso_File_System_Impl - fs_spi = File_System_SPI - data = Meta.lookup_services File_System_SPI """, @@ -51,10 +48,7 @@ public void loadFileSystemServices() throws Exception { .uri(uri) .buildLiteral(); - var module = ctx.eval(src); - var fileSystemSpi = module.invokeMember(MethodNames.Module.EVAL_EXPRESSION, "fs_spi"); - assertTrue("It is a type: " + fileSystemSpi, fileSystemSpi.isMetaObject()); - var fileSystemSpiRaw = (Type) ContextUtils.unwrapValue(ctx, fileSystemSpi); + var mod = ctx.eval(src); var ensoCtx = ContextUtils.leakContext(ctx); for (var p : ensoCtx.getPackageRepository().getLoadedPackagesJava()) { @@ -62,25 +56,20 @@ public void loadFileSystemServices() throws Exception { .services() .foreach( pw -> { - var m = - ensoCtx - .getTopScope() - .getModule("Standard.Base.Enso_Cloud.Enso_File_System_Impl") - .get(); - var all = m.getScope().getAllTypes(); + var spiType = findType(pw.provides(), ensoCtx); + var implType = findType(pw.with(), ensoCtx); var fsImpl = ContextUtils.executeInContext( ctx, () -> { - var conversion = UnresolvedConversion.build(m.getScope()); + var conversion = + UnresolvedConversion.build(implType.getDefinitionScope()); var state = State.create(ensoCtx); var node = InteropApplicationNode.getUncached(); - for (var t : all) { - var fn = conversion.resolveFor(ensoCtx, fileSystemSpiRaw, t); - var conv = node.execute(fn, state, new Object[] {fileSystemSpiRaw, t}); - if (conv != null) { - return conv; - } + var fn = conversion.resolveFor(ensoCtx, spiType, implType); + var conv = node.execute(fn, state, new Object[] {spiType, implType}); + if (conv != null) { + return conv; } return null; }); @@ -94,4 +83,12 @@ public void loadFileSystemServices() throws Exception { }); } } + + private Type findType(String name, EnsoContext ensoCtx) { + var moduleName = name.replaceFirst("\\.[^\\.]*$", ""); + var typeName = name.substring(moduleName.length() + 1); + var module = ensoCtx.getTopScope().getModule(moduleName).get(); + var implType = module.getScope().getType(typeName, true); + return implType; + } } From 5b7fd35850ba9dcbb338bc40911b1761caedde21 Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Tue, 17 Dec 2024 08:09:16 +0100 Subject: [PATCH 3/4] Missing with field Co-authored-by: Hubert Plociniczak --- lib/scala/pkg/src/main/scala/org/enso/pkg/Config.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/scala/pkg/src/main/scala/org/enso/pkg/Config.scala b/lib/scala/pkg/src/main/scala/org/enso/pkg/Config.scala index 39e8977a92b0..19477ae5173d 100644 --- a/lib/scala/pkg/src/main/scala/org/enso/pkg/Config.scala +++ b/lib/scala/pkg/src/main/scala/org/enso/pkg/Config.scala @@ -45,7 +45,7 @@ object ProvidesWith { w <- bindings .get(Fields.With) .map(str.decode) - .getOrElse(Left(new IOException("Missing provides"))) + .getOrElse(Left(new IOException("Missing `with` field"))) } yield ProvidesWith(p, w) } } From f71de2ef9fbb52b85abd81b3b4350008fc5b5976 Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Tue, 17 Dec 2024 08:09:36 +0100 Subject: [PATCH 4/4] Missing provides field Co-authored-by: Hubert Plociniczak --- lib/scala/pkg/src/main/scala/org/enso/pkg/Config.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/scala/pkg/src/main/scala/org/enso/pkg/Config.scala b/lib/scala/pkg/src/main/scala/org/enso/pkg/Config.scala index 19477ae5173d..b4705745f444 100644 --- a/lib/scala/pkg/src/main/scala/org/enso/pkg/Config.scala +++ b/lib/scala/pkg/src/main/scala/org/enso/pkg/Config.scala @@ -41,7 +41,7 @@ object ProvidesWith { p <- bindings .get(Fields.Provides) .map(str.decode) - .getOrElse(Left(new IOException("Missing provides"))) + .getOrElse(Left(new IOException("Missing `provides` field"))) w <- bindings .get(Fields.With) .map(str.decode)