Search code examples
javamavenbukkitgraalvm

GraalVM - No language and polyglot implementation was found on the classpath


I'm trying to use GraalVM inside a project to add simple scripting capabilities. I'm using Maven for dependency management to load Graal's base dependencies. Here is my pom.xml:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>cx.matthew</groupId>
  <artifactId>graaltest</artifactId>
  <version>1.0-SNAPSHOT</version>

  <properties>
    <graalvm.version>19.0.2</graalvm.version>
    <compiler.dir>${project.build.directory}/compiler</compiler.dir>
    <maven.compiler.source>1.6</maven.compiler.source>
    <maven.compiler.target>1.6</maven.compiler.target>
  </properties>

  <repositories>
    <repository>
      <snapshots>
        <enabled>false</enabled>
      </snapshots>
      <id>central</id>
      <name>Maven Repository Switchboard</name>
      <url>http://repo1.maven.org/maven2</url>
    </repository>
    <repository>
      <id>papermc</id>
      <url>https://papermc.io/repo/repository/maven-public/</url>
    </repository>
  </repositories>

  <dependencies>
    <dependency>
      <groupId>org.graalvm.sdk</groupId>
      <artifactId>graal-sdk</artifactId>
      <version>${graalvm.version}</version>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupId>org.graalvm.js</groupId>
      <artifactId>js</artifactId>
      <version>${graalvm.version}</version>
      <scope>runtime</scope>
    </dependency>
    <dependency>
      <groupId>org.graalvm.js</groupId>
      <artifactId>js-scriptengine</artifactId>
      <version>${graalvm.version}</version>
      <scope>runtime</scope>
    </dependency>
    <dependency>
      <groupId>org.graalvm.tools</groupId>
      <artifactId>profiler</artifactId>
      <version>${graalvm.version}</version>
      <scope>runtime</scope>
    </dependency>
    <dependency>
      <groupId>org.graalvm.tools</groupId>
      <artifactId>chromeinspector</artifactId>
      <version>${graalvm.version}</version>
      <scope>runtime</scope>
    </dependency>
    <dependency>
      <groupId>org.graalvm.truffle</groupId>
      <artifactId>truffle-api</artifactId>
      <version>19.0.0</version>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupId>com.destroystokyo.paper</groupId>
      <artifactId>paper-api</artifactId>
      <version>1.14.2-R0.1-SNAPSHOT</version>
      <scope>provided</scope>
    </dependency>
  </dependencies>

  <build>
    <resources>
      <resource>
        <directory>src/main/resources</directory>
        <filtering>true</filtering>
      </resource>
    </resources>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-shade-plugin</artifactId>
        <version>3.2.1</version>
        <executions>
          <execution>
            <phase>package</phase>
            <goals>
              <goal>shade</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>

</project>

And here is my code calling Graal:

package cx.matthew.graaltest;

import org.bukkit.plugin.java.JavaPlugin;
import org.graalvm.polyglot.Context;

public class GraalTestPlugin extends JavaPlugin {

  @Override
  public void onEnable() {
    Context context = Context.create();
    context.eval("js", "console.log(\"hi!!!\");");
  }

}

While this should work as far as I can tell, when running it, it outputs the following error:

[19:25:16 ERROR]: Error occurred while enabling GraalTest v1.0-SNAPSHOT (Is it up to date?)
java.lang.IllegalStateException: No language and polyglot implementation was found on the classpath. Make sure the truffle-api.jar is on the classpath.
    at org.graalvm.polyglot.Engine$PolyglotInvalid.noPolyglotImplementationFound(Engine.java:800) ~[?:?]
    at org.graalvm.polyglot.Engine$PolyglotInvalid.buildEngine(Engine.java:732) ~[?:?]
    at org.graalvm.polyglot.Engine$Builder.build(Engine.java:505) ~[?:?]
    at org.graalvm.polyglot.Context$Builder.build(Context.java:1304) ~[?:?]
    at org.graalvm.polyglot.Context.create(Context.java:697) ~[?:?]
    at cx.matthew.graaltest.GraalTestPlugin.onEnable(GraalTestPlugin.java:10) ~[?:?]
    at org.bukkit.plugin.java.JavaPlugin.setEnabled(JavaPlugin.java:263) ~[patched_1.14.2.jar:git-Paper-97]
    at org.bukkit.plugin.java.JavaPluginLoader.enablePlugin(JavaPluginLoader.java:338) ~[patched_1.14.2.jar:git-Paper-97]
    at org.bukkit.plugin.SimplePluginManager.enablePlugin(SimplePluginManager.java:419) ~[patched_1.14.2.jar:git-Paper-97]
    at org.bukkit.craftbukkit.v1_14_R1.CraftServer.enablePlugin(CraftServer.java:464) ~[patched_1.14.2.jar:git-Paper-97]
    at org.bukkit.craftbukkit.v1_14_R1.CraftServer.enablePlugins(CraftServer.java:378) ~[patched_1.14.2.jar:git-Paper-97]
    at org.bukkit.craftbukkit.v1_14_R1.CraftServer.reload(CraftServer.java:854) ~[patched_1.14.2.jar:git-Paper-97]
    at org.bukkit.Bukkit.reload(Bukkit.java:610) ~[patched_1.14.2.jar:git-Paper-97]
    at org.bukkit.command.defaults.ReloadCommand.execute(ReloadCommand.java:54) ~[patched_1.14.2.jar:git-Paper-97]
    at org.bukkit.command.SimpleCommandMap.dispatch(SimpleCommandMap.java:159) ~[patched_1.14.2.jar:git-Paper-97]
    at org.bukkit.craftbukkit.v1_14_R1.CraftServer.dispatchCommand(CraftServer.java:736) ~[patched_1.14.2.jar:git-Paper-97]
    at org.bukkit.craftbukkit.v1_14_R1.CraftServer.dispatchServerCommand(CraftServer.java:698) ~[patched_1.14.2.jar:git-Paper-97]
    at net.minecraft.server.v1_14_R1.DedicatedServer.handleCommandQueue(DedicatedServer.java:457) ~[patched_1.14.2.jar:git-Paper-97]
    at net.minecraft.server.v1_14_R1.DedicatedServer.b(DedicatedServer.java:419) ~[patched_1.14.2.jar:git-Paper-97]
    at net.minecraft.server.v1_14_R1.MinecraftServer.a(MinecraftServer.java:1061) ~[patched_1.14.2.jar:git-Paper-97]
    at net.minecraft.server.v1_14_R1.MinecraftServer.run(MinecraftServer.java:905) ~[patched_1.14.2.jar:git-Paper-97]
    at java.lang.Thread.run(Thread.java:834) [?:?]

Now, of course the suggested solution for this as the error says is to include truffle-api.jar in the classpath, however since this is running in a plugin environment, it is less than ideal to have end users of this plugin have to set up their classpath manually. Shading in the truffle-api Maven dependency doesn't seem to work, which is usually the solution to something like this.

There have been some existing proposed solutions. The first I came across was in this answer, but as you can see in my pom.xml, all of these dependencies are already included.

The other solution I've seen (as well as the problem) are outlined in this GitHub issue, but while I can confirm the modified META-INF/truffle/language file is in my final jar, it doesn't seem to be working. This is the file that is output in the jar:

language1.characterMimeType.0=application/tregex
language1.className=com.oracle.truffle.regex.RegexLanguage
language1.id=regex
language1.implementationName=
language1.interactive=false
language1.internal=true
language1.name=REGEX
language1.version=0.1

language2.characterMimeType.0=application/javascript
language2.characterMimeType.1=application/javascript+module
language2.characterMimeType.2=text/javascript
language2.className=com.oracle.truffle.js.lang.JavaScriptLanguage
language2.defaultMimeType=application/javascript
language2.dependentLanguage.0=regex
language2.fileTypeDetector0=com.oracle.truffle.js.lang.JSFileTypeDetector
language2.id=js
language2.implementationName=GraalVM JavaScript
language2.interactive=true
language2.internal=false
language2.name=JavaScript
language2.version=inherit

So, I'm kind of at a loss for what to do at this point. Does anyone have any ideas for how to solve this issue?


Solution

  • As of right now, the best way I've found of getting around this is to swap the context ClassLoader out for the correct one and then swap it back:

    ClassLoader oldCl = Thread.currentThread().getContextClassLoader();
    Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
    // Insert Graal code here
    Thread.currentThread().setContextClassLoader(oldCl);
    

    You can even set it up as a callback so you don't have to have that same code over and over:

    private void runInPluginContext(ContextCallback callback) {
      ClassLoader oldCl = Thread.currentThread().getContextClassLoader();
      Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
      callback.call();
      Thread.currentThread().setContextClassLoader(oldCl);
    }
    
    interface ContextCallback {
    
      void call();
    
    }
    

    And run it using:

    runInPluginContext(() -> {
      // Insert Graal code here
    });
    

    Obviously this is not ideal because it requires you to change context every time you call Graal code. If anyone has a better solution, I'd love to see it!