1
votes

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);
}
1

1 Answers

0
votes

After a short discussion on sbt/sbt gitter channel I was told to use:

fork in (Compile, run) := true

but at the same time acquire runner as:

val r = (runner in (Compile, run)).value 

That worked :)