pascal
pascal

Reputation: 87

Classpath issue when binding an instance in a Scala Interpreter

I already posted this question in scala lang forum but unfortunately I did not get any answer. Second chance ?

I try to embed an interpreter an evaluate a scala snippet into this interpreter. I would like to bind an instance of a custom class within the interpreter. To sum up that would look like:

import scala.tools.nsc._
import scala.tools.nsc.interpreter._

class C {
  def sayHello(s:String) = "hello "+s
}

object Main extends App {

 val c= new C

 val s = new Settings
 s.usejavacp.value=true

 val i = new IMain(s)
 i.bind("myC",c)
 i.bind("world","the world")

 val script = "println(myC.sayHello(world))"
 i.eval(script)   

}

When I run this snippet inside Eclipse (Kepler) - OpenJDK6/7 works for both - BSD OS Scala-2.11.0-M4 - scala-compiler.jar in the path it works fine If I try to run the same code inside the repl or directly with a scalac file.scala then scala -cp . Main I get the following error

    error: not found value myC
    javax.script.ScriptException: compile-time error
    at scala.tools.nsc.interpreter.IMain.compile(IMain.scala:575)
    at scala.tools.nsc.interpreter.IMain.eval(IMain.scala:997)
    at javax.script.AbstractScriptEngine.eval(AbstractScriptEngine.java:264)

I was able to make it work under another OS (Win7) but by adding s.bootclasspath="path/to/my/classes"

I suspect some classpath issue

Later I was able to make it run by replacing the scala command line call by a java call this way: java -cp $CLASSPATH Main with CLASSPATH containing scala libraries it works

I looked at scala command and it is like it appends scala libraries in the java path in a different way.

Does anyone has any advice ?

Thanks

Added based on following comments:

Scalac does not output any error In fact if I run:

  java -cp .:$SCALA_PATH/lib/scala-library.jar:$SCALA_PATH/lib/scala-compiler.jar:$SCALA_PATH/lib/scala-reflect.jar Main

or as suggested scala -nobootcp it works (Thanks for the valuable advice) Otherwise if I let scala use the bootcp the started line is the following one and it fails

  java -Xbootclasspath/a:/usr/home/pcohen/Dev/Scala/scala-2.11.0-M4/lib/akka-actors.jar[...] -classpath "" [...]

When the scala jars are appended to the bootclasspath, it is like my binding is failing. I am not able to clearly understand why this bootclasspath difference affects my classes.

Upvotes: 1

Views: 1394

Answers (1)

som-snytt
som-snytt

Reputation: 39577

I don't know if this qualifies as advice, but are you aware:

scala> class C { def sayHello(s: String) = s"hello, $s" }
defined class C

scala> $intp.bind("world","the world")
world: String = the world
res0: scala.tools.nsc.interpreter.IR.Result = Success

scala> val c = new C
c: C = C@19878659

scala> $intp interpret "c sayHello world"
res2: String = hello, the world
res1: scala.tools.nsc.interpreter.IR.Result = Success

With separate compilation, your example works with scala -nobootcp my.Main.

You can see more with -Dscala.repl.debug=true. You'll see the Error return from the bind (everyone checks result values, right? println(i.bind("myC",c))) caused by:

java.lang.ClassCastException: intpbind.C cannot be cast to intpbind.C

and the wrapper code that is attempting it.

More words:

You said, "it is like my binding is failing," but as I showed above, it's exactly that the binding fails.

The reason is that when you say "bind", you really mean, "Autogenerate a bit of code just as if I'd written, scala> val myC = c." Oh, and compile it, and then make myC a symbol that can get imported by the rest of the REPL session.

When you compile that code with the REPL on the boot class path, the boot class path must be able to see your class C. (It must also be the same class loaded by your application class loader, the one that loads your main class; that is usually true by virtue of delegation.)

So, this works:

scalac -d /tmp/out mytest.scala
scala -J-Xbootclasspath/a:/tmp/out mytest.Test

You could nuance that by putting only some of your classes on the boot class path, or, which is more likely, you'll do as I suggested and take scala off the boot class path.

I haven't researched this, but you could google for people with similar problems using the REPL embedded in managed environments where it really matters what class loader you reside in.

Bonus utility:

package scala {
  package object foo {
    def show(cl: ClassLoader): String = scala.reflect.runtime.ReflectionUtils.show(cl)
  }
}

to use a scala-private utility for just dumping your class loader of interest:

  import scala.foo._
  Console println show(getClass.getClassLoader)

Upvotes: 3

Related Questions