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?
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:
.