i am trying to compile scala code in runtime in java programm. I am using jsr232 api and my code looks like:
ScriptEngineManager manager=new ScriptEngineManager();
Scripted engine = (Scripted) manager.getEngineByName("scala");
engine.compile(sourceCode);
My pom looks like:
<properties>
<scala.version>2.13.10</scala.version>
</properties>
<dependencies>
<dependency>
<groupId>org.scala-lang</groupId>
<artifactId>scala-compiler</artifactId>
<version>${scala.version}</version>
</dependency>
<dependency>
<groupId>org.scala-lang</groupId>
<artifactId>scala-reflect</artifactId>
<version>${scala.version}</version>
</dependency>
<dependency>
<groupId>org.scala-lang</groupId>
<artifactId>scala-library</artifactId>
<version>${scala.version}</version>
</dependency>
</dependencies>
When i am running such code in IDE all is ok and i have no problems (all dependencies jars are in -classpath option) But after i try to package to jar with copy dependencies (so i have small jar with fat manifest and put all dependencies to lib folder).
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>3.2.0</version>
<executions>
<execution>
<id>copy-dependencies</id>
<phase>package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
</execution>
</executions>
<configuration>
<outputDirectory>${project.build.directory}/lib</outputDirectory>
<overWriteReleases>false</overWriteReleases>
<overWriteSnapshots>false</overWriteSnapshots>
<overWriteIfNewer>true</overWriteIfNewer>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.2.2</version>
<configuration>
<archive>
<manifest>
<addClasspath>true</addClasspath>
<classpathPrefix>lib/</classpathPrefix>
<mainClass>$$$MYCLASS$$$</mainClass>
</manifest>
<manifestEntries>
<Class-Path>.</Class-Path>
</manifestEntries>
</archive>
</configuration>
</plugin>
and run my application i got null on (Scripted) manager.getEngineByName("scala");
After some research i found that javax.script.ScriptEngineManager
contains scala engine factory;
But 'javax.script.ScriptEngineManager#getEngineByName(String)' returns null if something go wrong during method execution(without printstacktrace).
And my problem is on ScriptEngine engine = spi.getScriptEngine();
I manually try to create Scala script engine the same way as manager and Scala.Factory(Engine factory) do this with some debug settings:
if(engine==null) {
log.warn("No scala script engine exists. Try load again");
try {
Settings settings = new Settings();
settings.usejavacp().value_$eq(true);//same as scala code
settings.usemanifestcp().value_$eq(true); //same as scala code
settings.Yreplclassbased().value_$eq(true); //same as scala code
settings.verbose().value_$eq(true); //for more logs
settings.debug().value_$eq(true); //for more logs
Scripted.Factory fact = new Scripted.Factory();
engine= Scripted.apply(fact,settings, ReplReporterImpl.defaultOut());
}catch (Exception e){
log.error("Scala compiler wasn't initialized",e);
return;
}
}
I got next errors. Top level error is:
javax.script.ScriptException: Failed to compile ctx
at scala.tools.nsc.interpreter.shell.Scripted.<init>(Scripted.scala:89) ~[scala-compiler-2.13.10.jar:?]
at scala.tools.nsc.interpreter.shell.Scripted$.apply(Scripted.scala:278) ~[scala-compiler-2.13.10.jar:?]
at scala.tools.nsc.interpreter.shell.Scripted.apply(Scripted.scala) ~[scala-compiler-2.13.10.jar:?]
and some other errors from compiler:
java.lang.NullPointerException
at java.base/java.io.FilterInputStream.close(FilterInputStream.java:180)
at scala.reflect.io.ManifestResources$$anon$3.close(ZipArchive.scala:434)
at scala.tools.nsc.symtab.classfile.ReusableDataReader.reset(ReusableDataReader.scala:85)
......
java.io.IOException: class file 'file:..../target/lib/scala-library-2.13.10.jar(scala/Predef.class)' is broken
(class java.lang.NullPointerException/null)
at scala.tools.nsc.symtab.classfile.ClassfileParser.scala$tools$nsc$symtab$classfile$ClassfileParser$$handleError(ClassfileParser.scala:126)
at scala.tools.nsc.symtab.classfile.ClassfileParser$$anonfun$scala$tools$nsc$symtab$classfile$ClassfileParser$$parseErrorHandler$1.applyOrElse(ClassfileParser.scala:134)
at scala.tools.nsc.symtab.classfile.ClassfileParser$$anonfun$scala$tools$nsc$symtab$classfile$ClassfileParser$$parseErrorHandler$1.applyOrElse(ClassfileParser.scala:132)
at scala.runtime.AbstractPartialFunction.apply(AbstractPartialFunction.scala:35)
......
java.lang.NullPointerException
at java.base/java.io.FilterInputStream.close(FilterInputStream.java:180)
at scala.reflect.io.ManifestResources$$anon$3.close(ZipArchive.scala:434)
at scala.tools.nsc.symtab.classfile.ReusableDataReader.reset(ReusableDataReader.scala:85)
at scala.tools.nsc.symtab.classfile.ClassfileParser.$anonfun$parse$2(ClassfileParser.scala:161)
......
java.io.IOException: class file 'file:..../target/lib/scala-library-2.13.10.jar(scala/Unit.class)' is broken
(class java.lang.NullPointerException/null)
at scala.tools.nsc.symtab.classfile.ClassfileParser.scala$tools$nsc$symtab$classfile$ClassfileParser$$handleError(ClassfileParser.scala:126)
at scala.tools.nsc.symtab.classfile.ClassfileParser$$anonfun$scala$tools$nsc$symtab$classfile$ClassfileParser$$parseErrorHandler$1.applyOrElse(ClassfileParser.scala:134)
at scala.tools.nsc.symtab.classfile.ClassfileParser$$anonfun$scala$tools$nsc$symtab$classfile$ClassfileParser$$parseErrorHandler$1.applyOrElse(ClassfileParser.scala:132)
at scala.runtime.AbstractPartialFunction.apply(AbstractPartialFunction.scala:35)
at scala.tools.nsc.symtab.classfile.ClassfileParser.parse(ClassfileParser.scala:144)
It looks like scala compiler for some reason can not read jar source files.
Java version: openjdk version "11" 2018-09-25 OpenJDK Runtime Environment 18.9 (build 11+28) OpenJDK 64-Bit Server VM 18.9 (build 11+28, mixed mode)
Updated after Dmytro answer
It helps. Thanks alot.
But still got 1 issue with runtime classloading. I want user be able to add new dependency jar runtime.
On first version i do it like: engine.intp().addUrlsToClassPath(new ArraySeq.ofRef<>(url))
. And it works well(i mean well when run inside IDEA)
Now i try do it like:
private final ScalaCompilerClassLoader urlClassLoader = new ScalaCompilerClassLoader(new URL[]{},ClassLoader.getSystemClassLoader());
private static class ScalaCompilerClassLoader extends URLClassLoader {
public ScalaCompilerClassLoader(URL[] urls, ClassLoader parent) {
super(urls, parent);
}
public void add(URL url){
super.addURL(url);
}
}
and i use your code with my classLoader.
JavaUniverse.JavaMirror mirror = universe.runtimeMirror(urlClassLoader);
The issue is that i can add new jar to my urlClassLoader before first call to toolBox.eval(toolBox.parse(sourceCode))
.
After first toolBox.eval
call newly injected dependencies are not recognized by compiler.
And now i have to create new toolBox after each new jar dependency injecting.