My goal is to avoid FileNotFound exception when loading resource from inside JAR and running application from JAR in SBT.
I have a following task:
val generateSource = TaskKey[Unit]("generateSource")
val generateSourceImpl : Def.Initialize[Task[Unit]] = {
Def.task {
val localGeneratorJar = file(s"${baseDirectory.value.getParentFile.toString}/generator/repo/.sbt/generator.jar")
if (!localGeneratorJar.exists()) {
sys.error(s"Generator not found, please execute 'sbt publishLocal' in 'generator' project first.")
}
val r = (runner in Compile).value
val opts = Seq( /* options here */ )
toError(r.run(“com.example.AllMightyCompiler", Seq(localGeneratorJar), opts, streams.value.log))
}
}
Issue happens when AllMightyCompiler loads e.g. XSD schema from ‘localGeneratorJar’ itself and JAR is modified between the runs of the sbt task. Typical workflow is:
$ sbt
> generateSource
# all good, but
# when in meantime ‘generator.jar is modified’ task invocation will be
> generateSource
root > generateSource
[info] Running AllMightyCompiler —opts=dummy
com.example.GeneratorException: org.xml.sax.SAXParseException; schema_reference.4: Failed to read schema document 'jar:file:/Users/Me/Development/Work/generator/repo/.sbt/generator.jar!/module.xsd', because 1) could not find the document; 2) the document could not be read; 3) the root element of the document is not <xsd:schema>.
at com.example.ModuleLoader.<init>(ModuleLoader.java:127)
at com.example.AllMightyCompiler.loadModel(Compiler.java:394)
at com.example.AllMightyCompiler.generateImpl(Compiler.java:264)
at com.example.AllMightyCompiler.generate(Compiler.java:144)
at com.example.AllMightyCompiler.main(Compiler.java:134)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:483)
at sbt.Run.invokeMain(Run.scala:72)
at sbt.Run.run0(Run.scala:65)
at sbt.Run.sbt$Run$$execute$1(Run.scala:54)
at sbt.Run$$anonfun$run$1.apply$mcV$sp(Run.scala:58)
at sbt.Run$$anonfun$run$1.apply(Run.scala:58)
at sbt.Run$$anonfun$run$1.apply(Run.scala:58)
at sbt.Logger$$anon$4.apply(Logger.scala:90)
at sbt.TrapExit$App.run(TrapExit.scala:244)
at java.lang.Thread.run(Thread.java:744)
Caused by: org.xml.sax.SAXParseException; schema_reference.4: Failed to read schema document 'jar:file:/Users/Me/Development/Work/generator/repo/.sbt/generator.jar!/module.xsd', because 1) could not find the document; 2) the document could not be read; 3) the root element of the document is not <xsd:schema>.
at com.sun.org.apache.xerces.internal.util.ErrorHandlerWrapper.createSAXParseException(ErrorHandlerWrapper.java:203)
at com.sun.org.apache.xerces.internal.util.ErrorHandlerWrapper.error(ErrorHandlerWrapper.java:134)
at com.sun.org.apache.xerces.internal.impl.XMLErrorReporter.reportError(XMLErrorReporter.java:437)
at com.sun.org.apache.xerces.internal.impl.XMLErrorReporter.reportError(XMLErrorReporter.java:347)
at com.sun.org.apache.xerces.internal.impl.xs.traversers.XSDHandler.reportSchemaErr(XSDHandler.java:4166)
at com.sun.org.apache.xerces.internal.impl.xs.traversers.XSDHandler.reportSchemaError(XSDHandler.java:4149)
at com.sun.org.apache.xerces.internal.impl.xs.traversers.XSDHandler.getSchemaDocument1(XSDHandler.java:2479)
at com.sun.org.apache.xerces.internal.impl.xs.traversers.XSDHandler.getSchemaDocument(XSDHandler.java:2187)
at com.sun.org.apache.xerces.internal.impl.xs.traversers.XSDHandler.parseSchema(XSDHandler.java:573)
at com.sun.org.apache.xerces.internal.impl.xs.XMLSchemaLoader.loadSchema(XMLSchemaLoader.java:616)
at com.sun.org.apache.xerces.internal.impl.xs.XMLSchemaLoader.loadGrammar(XMLSchemaLoader.java:574)
at com.sun.org.apache.xerces.internal.impl.xs.XMLSchemaLoader.loadGrammar(XMLSchemaLoader.java:540)
at com.sun.org.apache.xerces.internal.jaxp.validation.XMLSchemaFactory.newSchema(XMLSchemaFactory.java:255)
at javax.xml.validation.SchemaFactory.newSchema(SchemaFactory.java:638)
at javax.xml.validation.SchemaFactory.newSchema(SchemaFactory.java:670)
at n4.generator.util.ModuleLoader.<init>(ModuleLoader.java:121)
... 17 more
As I’ve figured out there are 2 possible solutions:
- Disabling caching by JAR URLs directly in com.example.ModuleLoader
- Somehow fork runner in my sbt task ‘generateSources'
Option 1 can be accomplished with:
new URLConnection(schemaURL) {
@Override
public void connect() throws IOException {
// Do nothing
}
}.setDefaultUseCaches(false);
But, option 1 is not what I want while it requires modification of ‘generator.jar’, which I can do now, but maybe not in future. Thus only remaining option is using forked JVM for runner in task, but when I set in build.sbt:
fork in Compile := true
obtained runner stil use same JVM as sbt itself.
Does anybody have an idea how can I force runner to start in separate JVM?
P.S. I only suppose that forking will help base on http://www.scala-sbt.org/0.13/docs/Running-Project-Code.html because when I run my task first time I see:
Exception: sbt.TrapExitSecurityException thrown from the UncaughtExceptionHandler in thread "run-main-0"
This is due to fact that AllMightyCompiler currently ends with following line:
public static void main(String... args) {
… lot of code ...
System.exit(exit);
}