Hi All,
I recently started exploring Groovy and I decided to write a small application using JavaFX for the GUI layer.
I am using Oracle JDK 8, Groovy 2.3 and Gradle 2.1.
My first experiment was quite minimalistic: I converted the JavaFX Hello World example from Oracle [1] to Groovy
and created a very simple Gradle project to compile the code.
The project is available on GitHub [2].
Unfortunately, when compiling the code, I get an error:
[andrea@sirio hellofx-groovy-gradle]$ gradle clean build
:clean
:compileJava UP-TO-DATE
:compileGroovy
startup failed:
/path/to/hellofx-groovy-gradle/src/main/groovy/helloworld/HelloWorldFx.groovy: 5: unable to resolve class javafx.scene.control.Button
@ line 5, column 1.
import javafx.scene.control.Button
^
and more like the above for the other JavaFX classes.
I then tried to compile the same source code from the command line using the `
groovyc` Groovy compiler and it worked flawlessly.
I also tried an equivalent
"pure Java" project using the original program from the Oracle page and even in this case everything worked OK.
To recap: only when compiling Groovy code with Gradle, the JavaFX classes are not visible.
This is what I have found going down this rabbit hole :)
JavaFX packages are contained in a separate
jfxrt.jar archive unlike e.g. Swing that is contained in the main rt.jar archive.
The way the
jfxrt.jar archive is made available to Java applications differs between JVM versions and vendors:
- Oracle JDK 7
jfxrt.jar is in ${java.home}/lib and therefore not available by default on the classpath.
Java 7 projects that want to use JavaFX must explicitly find and add jfxrt.jar to the compile
and runtime classpath.
- Oracke JDK 8 & 9
jfxrt.jar is in ${java.home}/lib/ext and is made available to Java programs via the
Extension Classloader that lies at the core of the Extension Mechanism [3].
Regular java programs (including the `javac` and `groovyc` compilers) have direct access
to the JavaFX classes with no need to fiddle with the classpath.
- OpenJDK 7
JavaFX is not available in this JVM.
- OpenJDK 8 & 9
JavaFX is currently not available in any OpenJDK build, but it could be theoretically included
in the future because the OpenJFX project [4] is now open source. It is up to whoever builds
a specific OpenJDK release to also include OpenJFX.
I was able to track down the issue with Gradle to the configuration of the classloader provided to the
JavaAwareCompilationUnitinstance created in the class
org.gradle.api.internal.tasks.compile.ApiGroovyCompiler.
The classloader (local variable "
compileClasspathClassLoader" in the
ApiGroovyCompiler.execute() method) is built without
the ExtClassLoader as one of its ancestors and therefore it bails out when it tries to load a JavaFX class.
I have implemented a simple fix [5] in ApiGroovyCompiler that adds the Extension Classloader as an ancestor of the compile classloader
only when JavaFX is available, therefore maintaining the previous behaviour when it is not.
Before I submit a PR I'd love to have some feedback on whether you think this is really an issue and on how I proceeded to fix it.
For reference there is a forum post and an associated bug that have probably the same root cause: [6] [7].
I hope this helps.
BTW, Thanks for a fantastic tool!
Andrea.
[1]
http://docs.oracle.com/javase/8/javafx/get-started-tutorial/hello_world.htm[2]
https://github.com/acisternino/helloworld-fx[3]
http://docs.oracle.com/javase/tutorial/ext/index.html[4]
http://openjdk.java.net/projects/openjfx[5]
https://github.com/acisternino/gradle/commit/f53837584b2c9d9b65ce15089a00a534d8092260[6]
http://forums.gradle.org/gradle/topics/noclassdeffounderror_on_a_javafx_class_in_compilegroovy_task_on_java_8[7]
http://issues.gradle.org//browse/GRADLE-3046