Tokee's

Sehen, wozu das führt…

Building and packaging of JavaFX applications with Gradle

| 4 Kommentare

Lately, I changed my favoured IDE from Netbeans to IntelliJ IDEA. Happy so far, I quickly realised that building and running my JavaFX applications was limited to IDEA, only. Compared to Netbeans, IDEA 12 does not provide the ability to create self-contained and double-clickable archives out-of-the-box.

Okay, some work to do… ‘Play around with Gradle’ was an item on my todo-list, so I created an initial build.gradle, which was a really simple one-liner.

apply plugin: 'java'

But, as you might have expected, this was not the final solution, since the creation of a self-contained, double-clickable archive requires some more steps of work – at least pointing to an installed JavaFX archive, required for building.

I found the JavaFX Ant Tasks at Oracle’s website. While I was on my way leaving building projects with Ant behind, this task seemed to be the simplest way of achieving the desired aim, and – as you might know, also – Gradle can do it’s job by importing Ant tasks.

Et voilà, after some hours of studying the Gradle documentation and searching the www, I have a solution consisting of two concise files (sources can be found below):

  • build.gradle
  • javafx.xml

project_layout_inv
Presuming a project layout as shown, having these scripts in your project folder, enables you to:

  • build the project with gradle build
  • run the project with gradle run (no matter, whether from console or IDEA)
  • install the project with gradle installApp
  • run the installed application via batch-file or shell-script
  • run the installed application by double-clicking the created archive
apply plugin: 'java'
apply plugin: 'application'

mainClassName = [REPLACE WITH YOUR MAIN CLASS]

def javaFxHome = "${System.properties['java.home']}";
//There seems to be an issue with Gradle detecting the JDK.
//So, we have to help a wee bit to find the ant-javafx.jar
//This may be different on your system.
def antJavaFxJar = "$javaFxHome/../lib/ant-javafx.jar"

def vendor = [REPLACE WITH VENDOR NAME]
def version = [REPLACE WITH VERSION NUMBER]
def title = [REPLACE WITH APPLICATION TITLE]

configurations {
	//we do not want to have jfxrt.jar in the classpath when creating the jar,
	//therefore a seperate configuration is required
    providedCompile
}

//this one works with file dependencies. If you prefer 
//e.g. Maven, feel free to change.
def libFolder = 'lib'
def includePattern = '*.jar'

dependencies {
    providedCompile files("$javaFxHome/lib/jfxrt.jar")
    compile fileTree(dir: libFolder, include: includePattern)
}

compileJava {
    //add required JavaFX libs to compile classpath
    sourceSets.main.compileClasspath += configurations.providedCompile
}

run {
    //add required JavaFX libs to runtime classpath
    classpath += configurations.providedCompile
}

//ant configuration for creating double-clickable, self-contained JAR
ant.importBuild 'javafx.xml'
ant.antJavaFxJar = antJavaFxJar
ant.mainClassName = mainClassName
ant.fallbackClassName = 'com.javafx.main.NoJavaFXFallback'
ant.distDir = libsDir
ant.distName = jar.archiveName;
ant.resourceDir = libFolder
ant.resourceIncludePattern = includePattern
ant.applicationTitle = title
ant.applicationVendor = vendor
ant.applicationVersion = version
ant.applicationClasses = sourceSets.main.output.classesDir
ant.applicationResources = sourceSets.main.output.resourcesDir

//clear existing task actions and call ant task
jar {
    // reset actions
    actions = []

    doLast {
        javafxjar.execute(); // <-- the task described in javafx.xml
}

//create some smarter looking start scripts
startScripts {
    doLast {
        unixScript.text = "java -jar ../lib/$jar.archiveName"
        windowsScript.text = "java -jar ..\\lib\\$jar.archiveName"
    }
}
<project name="JavaFXApplication"
         xmlns:fx="javafx:com.sun.javafx.tools.ant">
    <target name="javafxjar">

        <taskdef resource="com/sun/javafx/tools/ant/antlib.xml"
                 uri="javafx:com.sun.javafx.tools.ant"
                 classpath="${antJavaFxJar}"/>

        <fx:application id="application"
                        name="${applicationTitle}"
                        mainClass="${mainClassName}"
                        fallbackClass="${fallbackClassName}"/>

        <!-- show some info in gradle's output-->
        <echo message="creating package: ${distName}"/>

        <fx:jar destfile="${distDir}/${distName}">
            <fx:application refid="application"/>

            <!-- define the classpath to be placed in the manifest -->
            <fx:resources>
                <fx:fileset dir="${resourceDir}"
                            includes="${resourceIncludePattern}"/>
            </fx:resources>

            <manifest>
                <attribute name="Implementation-Vendor"
                           value="${applicationVendor}"/>
                <attribute name="Implementation-Title"
                           value="${applicationTitle}"/>
                <attribute name="Implementation-Version"
                           value="${applicationVersion}"/>
            </manifest>

            <!-- describe the content to be packed -->
            <fileset dir="${applicationClasses}"/>
            <fileset dir="${applicationResources}"/>
        </fx:jar>
    </target>
</project>

Refinements for testing, integration of the Gradle Wrapper, etc. are possible, and may be described in another blog post.

Enjoy!

4 Kommentare

  1. Pingback: Java desktop links of the week, December 24 | Jonathan Giles

  2. I like the approach you’ve taken here. It’s also possible to get rid of the ant configuration completely. I started a blog and posted instructions here:

    http://ryansblog.jptech.ca/2012/12/javafx-and-gradle.html

  3. Very cool. Thanks for publishing…

  4. Hello,
    interesting post. How would you make to have a single executable jar with all its dependencies embedded ?
    Thanks

Hinterlasse eine Antwort

Pflichtfelder sind mit * markiert.