1

I'm trying to write an interface (GUI) to run some Kotlin scripts. I'm starting with just a Junit test to ensure that I can execute a script. I can't even load the kotlin engine. It seems like my dependencies (gradle) are in order, but you can double check that.

compile name: 'kotlin-script-runtime',
    group: 'org.jetbrains.kotlin', version: kotlin_version
compile name: 'kotlin-script-util',
    group: 'org.jetbrains.kotlin', version: kotlin_version
compile name: 'kotlin-compiler-embeddable',
    group: 'org.jetbrains.kotlin', version: kotlin_version

above: ext.kotlin_version = '1.3.0'

Here is the failing test:

@Test
fun testEngine() {
  // attempt to load script engine class before looking for it
  val ktsEngineClassName = "org.jetbrains.kotlin.script.jsr223.KotlinJsr223JvmLocalScriptEngine";
  var clazz: Class<*>? = null;
  // assign the clazz variable inside a closure to catch ClassNotFound
  assertDoesNotThrow({clazz = Class.forName(ktsEngineClassName)}, "could not find $ktsEngineClassName")
  // just to make sure it's there
  assertNotNull(clazz, "could not find $ktsEngineClassName")

  // so far so good, but here's the problem:
  val engineName = "kotlin"
  // load the factory
  val factory = ScriptEngineManager().getEngineByName(engineName)?.factory
  // and test that we got it
  assertNotNull(factory, "didn't find factory for '$engineName'." +
      "\navailable: ${ScriptEngineManager().engineFactories}")
}

That last assertNotNull fails with the following output:

org.opentest4j.AssertionFailedError: didn't find factory for 'kotlin'.
available: [jdk.nashorn.api.scripting.NashornScriptEngineFactory@1a41b1fc] ==> expected: not <null>

You can see that, although the script engine class seems to load via Class.forName successfully, the name kotlin is not registered, and the list of available engines contains Nashorn only. How do I ensure that the engine is registered?

Travis Well
  • 947
  • 10
  • 32

1 Answers1

2

It looks like the kotlin script engine is not in the default classloader, so you need to specify a classloader when instantiating the ScriptEngineManager:

ScriptEngineManager(clazz.getClassLoader()) getEngineByName(engineName)?.factory

UPDATE

In Java 8, if you look at $JAVA_HOME/jre/lib/ext/nashorn.jar in the path META_INF/services there is a file called javax.script.ScriptEngineFactory. The contents are:

jdk.nashorn.api.scripting.NashornScriptEngineFactory

So when the ScriptEngineManager looks for ScriptEngine implementations, it scans the classpath for entries named /META-INF/services/javax.script.ScriptEngineFactory and loads the class names from each one found.

Normally, the jar containing your targeted script engine would contain its own services file, but, if your kotlin script engine is already in the class path, I think you could just create a classpath resource called /META-INF/services/javax.script.ScriptEngineFactory with the plain text context of

org.jetbrains.kotlin.script.jsr223.KotlinJsr223JvmLocalScriptEngineFactory

and you should be good to go. Also, you would not need any of the code you provided in your example above the comment // so far so good, but here's the problem:.

Travis Well
  • 947
  • 10
  • 32
Nicholas
  • 15,916
  • 4
  • 42
  • 66
  • Seemed like a promising suggestion, but didn't help (still just Nashorn available from that ScriptEngineManager). Did it work for you? – Travis Well Nov 01 '18 at 20:05
  • Uh, no. Not kotlin enabled. But had the same issue with 3rd party script jars. Now that I think about it, though, I think you also need a *services* file in the library jar since script engines use the service locator to find implementations. Let me dig up an example. – Nicholas Nov 01 '18 at 20:45
  • If that's true, it would be a bug with the 'kotlin-compiler-embeddable' dependency. Maybe I'll try to fix it. – Travis Well Nov 04 '18 at 17:34
  • I could do this just by adding the text file in the resources directory of my code repo. `mkdir -pv src/main/resources/META-INF/services` and then `echo org.jetbrains.kotlin.script.jsr223.KotlinJsr223JvmLocalScriptEngineFactory > src/main/resources/META-INF/services/javax.script.ScriptEngineFactory` – Travis Well Nov 06 '18 at 20:18
  • Update: There is a kotlin-scripting-jsr223 artifact that handles this. – Niclas Hedhman Apr 23 '22 at 12:31