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.
Could you try to replace
with
?
Does this change anything for you?
Using scala macro from java
How can I run generated code during script runtime?
How to compile and execute scala code at run-time in Scala3?
"eval" in Scala
Regarding your original code with JSR232 scripting. Could you check whether anything changes if you add
fork := true(sbt syntax) to the build file? I guess in Maven this should be<fork>true</fork>.Regarding your question with classloading. One option is to use protected method
addURLAnother is to create a child class loader, new runtime mirror and toolbox
Every toolbox has two mirrors (and two class loaders). The first is the one it was created with, i.e. the mirror of toolbox dependencies. The second is its own mirror
toolbox.mirror, where new definitions live (the class loader ismirror.classloader).See the following example, where I create a new class, instantiate it, add new JAR, and use previous definition