openjfx / openjfx-docs

Getting started guide for JavaFX 11
BSD 3-Clause "New" or "Revised" License
96 stars 25 forks source link

Provide example for Gradle/Java Modules/JavaFx(.fxml)/Kotlin #55

Open Sporking opened 6 years ago

Sporking commented 6 years ago

I have been struggling for several weeks (!) to get a previously working JavaFX application to build and run in a clean manner using:

I have encountered severe problems doing this, and at this point I have tried almost every idea I had as to how to accomplish it. Much googling has located for me a variety of fragments of Gradle scripts that accomplish one or two of these goals at a time, but which invariably are structured in such a way as to make the other goals difficult or impossible.

This should not be as difficult as it is, but lack of examples that use these items TOGETHER in a straightforward manner has made it an incredibly frustrating experience. For instance:

and so forth. Each of the above observations has involved many hours of googling and experimenting on my part to discover what was going on.

Granted, some of these are Gradle problems, and I will be filing some bug reports against Gradle in the near future, but at the same time, I have found that the documentation for how to use JavaFX with Gradle seems to be very lacking with regards to how to use the two together. Example scripts that use up-to-date JavaFX practices use out-of-date or deprecated Gradle practices, and vice versa. Assumptions are made as to whether or not .fxml files will be used in the project, whether Kotlin will be used, and so forth. This lack of good, clean, simple, complete, and well-supported examples is a severe impediment to using both Gradle and JavaFX.

I have scoured the internet searching for a single example that works and incorporates all of these items together and have so far not found a single one. I am hoping that someone on the JavaFX team who is more knowledgeable than I with JavaFX can provide some examples on the "Getting Started with JavaFX 11" page that will accomplish most if not all of these goals in a single self-consistent script.

My current code, which SOMEWHAT works, and which can perhaps be used as a starting point for documentation or can be mined for ideas, is as follows:

    import org.apache.tools.ant.types.resources.JavaResource
    import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
    import org.gradle.internal.os.OperatingSystem

    plugins {
        java
        kotlin("jvm") version "1.2.51"
        application
    }

    application {
        group = "org.example.something"
        version = "1.0-SNAPSHOT"
        applicationName = "Something"
        mainClassName = "org.example.something.Something"
    }

    // Note: OperatingSystem is a non-public Gradle API. I am using it here to avoid having to include an
    // external plugin to determine the operating system, such as com.google.osdetector. Personally,
    // I think that Gradle's OperatingSystem should be made public, and should include an instance method
    // for getting the platform name in a format suitable for use with JavaFX Jars (like the following)
    // so that using JavaFX with Gradle is simplified. There are already too many hurdles to jump through
    // in order to get JavaFX to work with Gradle.
    val os = OperatingSystem.current()!!
    val platform = when { os.isWindows -> "win"; os.isLinux-> "linux"; os.isMacOsX -> "mac"; else -> error("Unknown OS") }

    repositories {
        jcenter()
    }

    dependencies {
        implementation(kotlin("stdlib-jdk8"))
        implementation("org.openjfx:javafx-fxml:11:$platform")
        implementation("org.openjfx:javafx-web:11:$platform")
        implementation("org.openjfx:javafx-media:11:$platform")
        implementation("org.openjfx:javafx-swing:11:$platform")
        implementation("org.openjfx:javafx-base:11:$platform")
        implementation("org.openjfx:javafx-graphics:11:$platform")
        implementation("org.openjfx:javafx-controls:11:$platform")
        // other implementation dependencies go here...
    }

    configure<JavaPluginConvention> {
        sourceCompatibility = JavaVersion.VERSION_1_10
        targetCompatibility = JavaVersion.VERSION_1_10
    }

    // Put *.fxml files from src/main/java into a 'resources' subtree instead of alongside *.class files.
    // This is necessary because Gradle won't copy them from the subtree containing class files into
    // a location where getClass().getResource(...) can find them at runtime.
    sourceSets {
        getByName("main") {
            resources {
                srcDir(files("src/main/java"))
    //            outputDir = file("java") // Is this needed or helpful?
            }
        }
    }

    // The name of the top-level module (target module) we are building.
    val moduleName = "org.example.something"

    // This task will compile all Java code in the target module except for test code.
    // Note that we are supplying a module path (from the 'dependencies' section above) instead of
    // a class path to the Java compiler.
    tasks.named<JavaCompile>("compileJava") {
        inputs.property("moduleName", moduleName)
        doFirst {
            val modulePath = classpath.asPath
            classpath = files() // Clear classpath since we are using module path instead
            options.compilerArgs.addAll(listOf(
                "--module-path", modulePath // Supply accumulated class path as module path instead.
            ))
        }
    }

    // This task will compile all Java test code in the target module.
    // Note that we are supplying a module path (from the 'dependencies' section above) instead of
    // a class path to the Java compiler.
    tasks.named<JavaCompile>("compileTestJava") {
        // See https://stackoverflow.com/questions/46991022/junit-5-java-9-and-gradle-how-to-pass-add-modules
        inputs.property("moduleName", moduleName)
        doFirst {
            val modulePath = classpath.asPath
            classpath = files() // Clear classpath since we are using module path instead
            options.compilerArgs.addAll(listOf(
                "--module-path", modulePath // Supply accumulated class path as module path instead.
            ))
        }
    }

    // This task will compile all Kotlin code found in the Java source tree.
    tasks.withType<KotlinCompile> {
        // See https://stackoverflow.com/questions/47657755/building-a-kotlin-java-9-project-with-gradle/47669720#47669720
        destinationDir = tasks.withType<JavaCompile>().first().destinationDir // Use same dir for Kotlin classes as Java classes.
        kotlinOptions.jvmTarget = "1.8"
    }

    // This task will run the JavaFX version of the app.
    // In addition to supply ing the module path, we need to patch the top-level module to
    // include the resources directory (where .fxml files will be located) so they can be found
    // at runtime.
    tasks.named<JavaExec>("run") {
        doFirst {
            val modulePath = classpath.asPath
            jvmArgs = listOf(
                "--module-path", modulePath,
                "--add-modules", "ALL-MODULE-PATH",
                "--patch-module", "$moduleName=build/resources/main",
                "--module", "$moduleName/${application.mainClassName}"
            )
        }
    }

In summary, please modify your "Getting Started With JavaFX 11" examples page to provide a working example of a Gradle script that accomplishes these goals. Basically, I would like to see a "Hello, World" that is implemented using idiomatic Gradle, Kotlin, Java 9 modules, and JavaFX with .fxml files.

Thanks in advance.

eugener commented 6 years ago

Have you tried https://github.com/openjfx/javafx-gradle-plugin ? This was specifically developed to simplify using JavaFX 11

Sporking commented 6 years ago

Thanks for the pointer. I haven't tried the https://github.com/openjfx/javafx-gradle-plugin yet. It looks interesting, but I am wary.

Some positive things:

Some concerning things:

Of these issues, the one of greatest concern to me is the lack of detailed documentation as to what the plugin actually is supposed to do. Without a clear statement of what effects the plugin is expected to have on my Gradle script (what tasks it is supposed to modify the behavior of, and in what way, and how it will handle issues like .fxml files, etc.), I will have no effective way to determine whether or not the plugin is actually working correctly and/or meets my needs without reading its source code in depth. I will also have difficulty using it with other tools or plugins. Of course, if I was likely to fully understand what its source code is doing, I probably wouldn't have had as much trouble getting this to work as I already have (see above), and if it is omitting doing anything important, I won't be able to tell with my current level of knowledge. (Note that I have essentially the same complaint with other javafx plugins I have found, but most of them do provide at least a bit more documentation than this one does.)

From what little I can tell of it at the moment, the plugin seems like a promising start. I may take a little time to play with this and see if I can get it working, but I have already spent some time going down this road with other plugins, and have not yet had good results. I hope that the plugin improves soon (particularly with regards to its documentation) so that this seems more practical.