How to set up classpath for the Scala interpreter in a managed environment?
I am working on an extension for the Apache Wicket web framework, which lets the user execute code in several programming languages at runtime from the browser. One of these languages is Scala, but I am having trouble when it is bundled as a WAR file and deployed to a container such as Tomcat.
When the Scala interpreter is invoked, it refuses to run the code with the following message:
Failed to initialize compiler: object scala not found.
** Note that as of 2.8 scala does not assume use of the java classpath.
** For the old behavior pass -usejavacp to scala, or if using a Settings
** object programatically, settings.usejavacp.value = true.
After setting up usejavacp on the Scala settings, it still didn't work in a managed environment. The problem seems to be that the Scala interpreter cannot find the Scala library jars on the Java class path.
Searching the web, I found a proposal, which proposes the use of two classpath resouces named 'boot.class.path' and 'app.class.path', which should include the needed class开发者_如何学Pythonpath declarations. I tried this and it seemed to work. My problem with this solution, though, is that my extension is meant to get bundled into a WAR file and to be run in different environments, so it would be necessary for the user to modifiy these resources with respect to the environment it runs in. Also it would be a lot of work to include every single jar's path into the file.
Maybe I don't understand fully the proposal. Does anybody know of a solution for this?
I have managed to embed scala 2.9.1 within a war running in tomcat.
This is how I did it.
val code = """println("Hi");""";
val settings = new Settings
val compilerPath = java.lang.Class.forName("scala.tools.nsc.Interpreter").getProtectionDomain.getCodeSource.getLocation
val libPath = java.lang.Class.forName("scala.Some").getProtectionDomain.getCodeSource.getLocation
println("compilerPath=" + compilerPath);
println("settings.bootclasspath.value=" + settings.bootclasspath.value);
settings.bootclasspath.value = List(settings.bootclasspath.value, compilerPath, libPath) mkString java.io.File.pathSeparator
settings.usejavacp.value = true
val interpreter = new IMain(settings)
interpreter.interpret(code);
Just for the search engines. These were my exceptions before it worked.
Failed to initialize compiler: object scala not found.
** Note that as of 2.8 scala does not assume use of the java classpath.
** For the old behavior pass -usejavacp to scala, or if using a Settings
** object programatically, settings.usejavacp.value = true.
and
Exception in thread "Thread-26" java.lang.Error: typeConstructor inapplicable for <none>
at scala.tools.nsc.symtab.SymbolTable.abort(SymbolTable.scala:34)
at scala.tools.nsc.symtab.Symbols$Symbol.typeConstructor(Symbols.scala:877)
at scala.tools.nsc.symtab.Definitions$definitions$.scala$tools$nsc$symtab$Definitions$definitions$$booltype(Definitions.scala:157)
at scala.tools.nsc.symtab.Definitions$definitions$.init(Definitions.scala:814)
at scala.tools.nsc.Global$Run.<init>(Global.scala:697)
at scala.tools.nsc.interpreter.IMain.scala$tools$nsc$interpreter$IMain$$_initialize(IMain.scala:114)
at scala.tools.nsc.interpreter.IMain$$anonfun$initialize$1.apply$mcZ$sp(IMain.scala:127)
at scala.tools.nsc.interpreter.IMain$$anonfun$initialize$2.apply(IMain.scala:126)
at scala.tools.nsc.interpreter.IMain$$anonfun$initialize$2.apply(IMain.scala:126)
at scala.concurrent.ThreadRunner$$anon$2$$anonfun$run$2.apply(ThreadRunner.scala:45)
at scala.concurrent.ThreadRunner.scala$concurrent$ThreadRunner$$tryCatch(ThreadRunner.scala:31)
at scala.concurrent.ThreadRunner$$anon$2.run(ThreadRunner.scala:45)
at java.lang.Thread.run(Thread.java:662)
You could try to build and set the classpath manually:
val setting = new scala.tools.nsc.settings.MutableSettings(println(_))
settings.classpath.append("my/path")
and pass this Settings instance to the Scala compiler.
I ported to 2.10.2 and the solution provided by Peter did not work any more. I serached the web and everyone pointed to the solution Scott suggested - but it did not work for me, either. I'm using maven to copy all needed dependencies to the lib folder, which includes the scala library and compiler. Maven also sets the (relative) classpath to all those Jars in the Manifest.
I'm sure there is a more elegant way of doing this - especially accessing the right Manifest right away -, and there are probably some pitfalls, but it works for me:
//Get Jarpath
val jarfile = this.getClass.getProtectionDomain.getCodeSource.getLocation.getPath
val jarpath = jarfile.take(jarfile.lastIndexOf("/") + 1)
//Get classpath from Manifest
val resources = getClass.getClassLoader.getResources("META-INF/MANIFEST.MF");
val cpath = Buffer[String]()
//Get classpath from Manifest
while (resources.hasMoreElements()){
val manifest = new Manifest(resources.nextElement().openStream());
val attr = manifest.getMainAttributes.getValue("Class-Path")
//Convert to absolut paths
if (attr != null) {
cpath ++= attr.split(" ").map(p => {"file:" + {if(p(1) == '/') "" else jarpath} + p })
}
}
val settings = new Settings
settings.bootclasspath.value = cpath.mkString(java.io.File.pathSeparator)
settings.usejavacp.value = true
精彩评论