Let's wrap this up quickly

Tutorial: A guide to the Gradle JavaFX Plugin

When Oracle released JavaFX 2.2 they included tools to package JavaFX applications into native installers and executables. However, much like any cross platform solution there are many knobs to be turned and switches to be flipped. When building a JavaFX Native bundle, there are many places where slight deviations can result in major changes in functionality and reliability. This is a process that is best automated.

Oracle has provided a series of Ant tasks to help in this deployment process, but Ant is starting to show its age. Many organizations have moved to newer build systems that support convention over configuration patterns and less verbose configuration. The Gradle JavaFX Plugin (which is open sourced) is one such tool that aims to simplify the JavaFX packaging experience when using the Gradle build system.

The Many Pieces of a JavaFX Deployment

Creating and packaging a JavaFX application isn't quite like packaging up a good old Swing application for use by a customer. When creating Swing applications users were left to their own devices to create the distribution chain they needed. But this is keeping in line with when Swing was first developed. There were very few best practices and no preset processes from Sun to go the last mile to the user (other than applets). In addition, there are some aspects of a packaged deployment that are entirely unique to JavaFX.

The first notable difference in JavaFX is that it customizes the jar construction and signing process. The principal reason is that JavaFX prefers that applications use a different launching mechanism than the old main with args method. This new Application class is a refugee from JSR-296 and allows for the framework to manage threading issues and other framework specific tasks that the application developer shouldn’t normally need to worry about. In addition to creating the jar file, JavaFX offers a different way to sign them. Rather than signing each class file and resource separately, JavaFX signs the entire jar as a single blob. With a single signature to verify, the validation and verification occurs much faster. The same signing keys and infrastructures are used to create and verify the certificates.

The second notable difference is that JavaFX provides some packaging tools to speed up the parsing and handling of CSS styling. These compiled stylesheets are distinguished from the plain text versions by their 'bss' extension, where the B stands for binary. The custom jar application, as well as a stand alone css2bss tool, compiles the css file into the binary format.

Finally, the JavaFX packager takes all of the various jar files and resources used by the application and creates a platform-dependent file to distribute, install, and launch the application. These packages are complete with cross-platform flair-like start menu integration, dock and tray icons, menubar integration, and single click icons. However, some of this integration comes at a cost: if you want high quality integration you often need to provide extra files such as platform specific icon formats.

Convention Over Configuration

So that may seem like a daunting list of steps that you need to perform to assemble your JavaFX application. You may be wondering just how large the gradle file is that you need to build it is. Well, let's look at the build file for one of the classic JavaFX Samples: brickbreaker.

apply from: 'http://dl.bintray.com/content/shemnon/javafx-gradle/javafx.plugin'

The code above is not an abbreviated build file, this is the entire build file for the brickbreaker sample app. You can even look for yourself. This is not some magic trick where I am hiding the real build file somewhere else. This same plugin script goes into every gradle build where you are building a JavaFX application. The trick here is that if you follow a standard file layout and naming convention, the plugin will make all the needed assumptions for all of the other parts of the build process.

What file conventions are these? The standard Maven layout conventions are used as a foundation. The src/main/java and src/main/resources file layout provides appropriate locations for the source code and the other resources such as CSS files and images. Tasks that rely on source code or other sources are configured by default to look into those directories. One example is the task that compiles CSS into a binary version for JavaFX – if you make no configuration changes then all of the CSS files in the src/main/resources file will be compiled into BSS files. The original CSS files will also be stored in the jar so that any code not taking advantage of the binary form can still use the CSS files.  

Directory

Files

Purpose

src/main/java

Standard Java layout

Application source files

src/main/java

<project name>/Main.java

Default main file/Application

src/main/resources

Non-java files

Application resources. (Images, CSS, etc.)

src/deploy/package

shortcut*.png

Launcher and Dock icons

src/deploy/package

volume*.png

MacOSX VolumeIcon

src/deploy/package

setup*.png

Windows Installer Corner Icon

src/deploy/package/linux

*

Installer specific scripts for Linux

src/deploy/package/macosx

*

Installer specific scripts for Mac

src/deploy/package/windows

*

Installer specific scripts for Windows

Gradle JavaFX Plugin File Conventions

 

The file conventions also extend to areas not normally covered by the standard Maven conventions. Two areas are currently handled: first, there are some installer specific customizations that you can make directly with the installer scripts. These are stored in src/deploy/package/<platform>, where each platform gets their own subdirectory. The second set of files handled by convention are the launcher and installer icons. While you can store these icons in a platform-specific manner in the platform-specific directory, you can also store them as PNG files in src/deploy/package and the plugin will convert them into platform-specific binaries. The plugin will even convert them into multi-resolution icons on Windows and MacOSX.

Configuration When Necessary

There are always some aspects of your application that just cannot be handled via file layout conventions. For that, there are configuration options inside of the build.gradle file itself. The Gradle JavaFX Plugin does its best to provide sensible defaults, but in some cases there are no universal defaults. Gradle uses the notion of extensions in the build file to hold these configuration values. JavaFX is not the only plugin to provide these extensions – nearly all of the plugins provide it in some form. The Java, webapp, and Maven plugins each use the extension mechanism to configure items that just cannot be done in the file system. Some of the configurations include what version of the language to use, deployment descriptor information, and random bits of metadata for the generated Maven poms such as licensing and source control. As seen below the extension is basically a block of code with lots of variable assignments.

javafx {
    appID 'SampleApp'
    appName 'Sample Application'
    mainClass 'com.example.sample.Main'

    jvmArgs = ['-XX:+AggressiveOpts', '-XX:CompileThreshold=1']
    systemProperties = [ 'prism.disableRegionCaching':'true' ]
    arguments = ['-l', '--fast']

    embedLauncher = false

    // deploy/info attributes
    category = 'Demos'
    copyright = 'Copyright (c) 2013 Acme'
    description = 'This is a sample configuration, it is not real.'
    licenseType = 'Apache 2.0'
    vendor = 'Acme'
    installSystemWide = true
    menu = true
    shortcut = true

    // app icons
    icons {
        shortcut = ['shortcut-16.png', 'shortcut-32.png', 'shortcut-128.png', 'shortcut-256.png', 'shortcut-16@2x.png', 'shortcut-32@2x.png', 'shortcut-128@2x.png']
        volume = 'javafx-icon.png'
        setup = 'javafx-icon.png'
    }

    // applet and webstart stuff
    debugKey {
        alias = 'debugKey'
        //keyPass = 'password' // provide via command line
        keyStore = file('~/keys/debug.jks')
        //storePass = 'password'  // provide via command line
    }
    releaseKey {
        alias = 'production'
        //keyPass = 'password' // provide via command line
        keyStore = file('/Volumes/ProdThumbDrive/production.jks')
        //storePass = 'password'  // provide via command line
    }
    signingMode 'release'

    width = 800
    height = 600
    embedJNLP = false
    codebase = 'http://example.com/bogus/JNLP/Codebase'

    // arbitrary jnlp icons
    icon {
        href = 'src/main/resources/javafx-icon.png'
        kind = 'splash'
        width = 128
        height = 128
    }
    icon {
        href = 'shortcut-32@2x.png'
        kind = 'selected'
        width = 16
        height = 16
        scale = 1
    }
}

 

This listing is entirely fake and does not reflect any particular example or sample, but it does show every knob you can twist and switch you can flip. It is also important to note that none of these values are required as some sort of a default will be provided.

The first section contains general information about the application. The appName is the name presented to the user while the appID is used internally by the OS.

The next section lists the system properties, environmental variables, and command line arguments for the application. The JavaFX packager takes these values and uses them in appropriate ways based on the packaging choices. They are then available to the JavaFX application in the expected manner.

The third section of code is entirely metadata-oriented. These values are not used at runtime but are used by the platform-specific packages. For example, the Windows installers use the publisher in the Add/Remove Programs control panel applet. The type of the license is important to the Linux packages. The tree booleans at the end adjust where in the system the package is installed, although not all the packagers pay attention to those values. For example, the Mac OSX DMG packager will not create dock shortcuts for you.

Next, we describe the icons. Icons can both be defined by file convention and via configuration. Why would you want to define your icons in the build file when a suitable file convention exists? The main reason is that you may want to re-use image files across multiple kinds of icons.To set the images used in a kind of icon, you assign a string value (or a list of string values) to the kind of icon you are using. These string values are URLs and if they appear to be relative URLs, they will be resolved against the src/deploy/package directory. When setting a list of images for an icon, all of the appropriately sized images will be used when creating a multi-resolution icon for a specific installer.

The final segment of code deals with JNLP and applet packaging. Most of this is simply passed through to the JavaFX packager to allow for the generation of the JNLP files. This includes an alternate layout of the icon format that has more of the fields required by a JNLP file. The releaseKey, debugKey, and signingMode configurations are used for signing the applet during the custom signjar step. While the configuration allows you to store the signing key passwords in the build file, it is generally a bad idea to do so. There are a myriad of ways to pass this information to the build system, and your choice of method will be driven by you and your organizations security requirements. But seriously, don't check your signing keys into source control. For a debug key the Gradle JavaFX Plugin will create a self-signed debug key for you if one is not specified. You must, however, provide your own release key.

Future Plans

 My future plans for the Gradle JavaFX plugin are in general to make it the easiest way to generate native packages for your JavaFX applications. This applies to whether it is for commercial projects, internal projects, or your own personal entertainment.

As with any open source project it is important to supply a list of “patches welcome” features that the current committers do not intend to address. For me, this list includes anything related to JNLP and applet packaging. Since none of the applications I work on have a real requirement for WebStart compatibility, I don't have a real good testbed to say if a particular feature is working well or not. It would be best if the code for those parts come from someone with a vested interest in WebStart deployment. As a consequence of this, I also would welcome patches (and sample code) relating to the use of the JavaFX pre-loader and integration with JVMs where the SecurityManager is enabled.

What features can you expect to see from future versions that don't come from patches? First there are some existing features that need support. The current packager code from Oracle has support for license texts and click-through licenses. The problem is that each platform has different requirements, Linux likes text while Windows refuses to let go of RTF. I would like to be able to have the developer provide a simple text files and the plugin will convert to the other formats.

The second feature on the roadmap is a mechanism to deal with platform-specific values. License file formats are just the beginning. Windows installers need GUIDs for the app identifier, whereas Mac bundles need a format that looks more like a Java package name. The categories in Mac and Linux also need to come from a finite set of values, and the build ideally would let you know when you stray from that path. To support this feature, there will also be a mechanism added to specify the JRE to be bundled with the installer packages. This would be useful to do things like build a RaspberryPi distribution in an Ubuntu VM.

Most importantly, the Gradle JavaFX plugin will do whatever it takes to support JavaFX on mobile platforms, such as Android and iOS. Ideally all of the ugly details will be already managed by the JavaFX packager, but whatever steps are not handled will get some attention from this plugin. I would imagine that there are features like final packaging for submission to the App Store and Google Play that are outside of the realm of the core packager, but there are a lot of steps that can be done before the files get uploaded to the store servers.

Summary

The Gradle JavaFX Plugin provides high quality integration of the esoteric pieces of the new JavaFX packaging tools. It integrates with current file convention best practices, as well as providing configuration options you may need to create a high quality deployment package. Whether you have a small toy app you want to share with friends, or a cross platform application you need to launch on Mac, Windows, and Linux, the Gradle JavaFX Plugin can help you do it.

Photo by Beck Gusler.

Danno Ferrin
Danno Ferrin

What do you think?

JAX Magazine - 2014 - 06 Exclucively for iPad users JAX Magazine on Android

Comments

Latest opinions