© Stephen Chin, Johan Vos and James Weaver 2019
S. Chin et al.The Definitive Guide to Modern Java Clients with JavaFXhttps://doi.org/10.1007/978-1-4842-4926-0_10

10. Packaging Apps for the Desktop

Stephen Chin1 , Johan Vos2 and James Weaver3
(1)
Belmont, CA, USA
(2)
Leuven, Belgium
(3)
Marion, IN, USA
 

Written by José Pereda and Johan Vos

Creating a Java client application is only the first step in the lifecycle of the application. Your application will be used by end users who interact with it. Whether your application is intended for internal, intranet-only usage or for a wide public audience, you want the barrier to use your application to be as low as possible. End users don’t like complex procedures before they can start working with an application.

In this chapter, we will explain how you can make it easy for the end user to install and use your application by using the same approach as they use for installing other (non-Java) applications. We will explain two important modern tools that are developed for helping Java developers to create bundled applications that fulfil the expectations of your users.

Web vs. Desktop Applications

When you create a Java client application, or by extension any client application, the end user will access your application directly. This is very different from creating cloud or web-based applications, where your code is probably running on a cloud or server infrastructure and the end user accesses your code typically via a web browser. In that case, you have to provision your application to a cloud or server, and there are specific tools that help you with this task.

You or your IT department controls the environment in which your application runs. You know the surrounding context, including the operating system, resource constraints, and available software. This situation is shown in Figure 10-1.
../images/468104_1_En_10_Chapter/468104_1_En_10_Fig1_HTML.jpg
Figure 10-1

The environment in which an application runs

In the case of a client application, you need to make sure that your application can be installed on all client systems. If you are very lucky, your application will be deployed on an intranet that is completely controlled by you or your IT organization. But typically, you do not control the environment in which your application is supposed to run. It is often assumed that your application should work on all operating systems, and you don’t know if the end user installed Java or not and, if so, what version or versions. Contrary to deployment in a cloud environment, you cannot install your application yourself on the system of the end user. You rely on this end user to execute the required instructions; hence, you want them to be simple and familiar as possible. The client deployment scenario is shown in Figure 10-2.
../images/468104_1_En_10_Chapter/468104_1_En_10_Fig2_HTML.jpg
Figure 10-2

Deployment scenario on desktop

Evolutions in Application Deployment

In the past, most deployment of Java client applications required a clear separation between your application code, its dependencies, and the Java Virtual Machine (JVM) it was required to run. Starting with Java Applets in 1995 and later with Java Web Start, developers of Java client applications could assume that there was some Java Virtual Machine already installed on the end user system. If this was not the case, the developer could assume some tool existed to assist the end user in installing a JVM.

The developer had to use build tools to bundle his code, dependencies, and resources into an application and then allow end users to install that application on top of the JVM they already installed – see Figure 10-3.
../images/468104_1_En_10_Chapter/468104_1_En_10_Fig3_HTML.jpg
Figure 10-3

Legacy development process

The rationale behind this concept was clear: when there are multiple Java applications running on a client, it makes sense to have a single JVM installation to provide the foundation for the different applications. With Java’s strong focus on backward compatibility, it was also often fair to assume that your applications would still run with later versions of the JVM. As a consequence, a single JVM on the end user system would be enough to serve the needs of all Java client applications. As a result, this concept requires less disk space (the JVM is shared between applications) and less bandwidth (the JVM code does not need to be transferred with the application; it is assumed to be present).

In recent years, there have been a number of evolutions that make this concept less applicable:
  • Typical disk space and bandwidth are increasing. Today’s average users are used to higher volumes of both storage capacity and streaming capacity. One of the drivers for this is the increase in media streaming and storage. Compared to the requirements of typical streaming services, the transmission and storage of the JVM code is relatively small. Hence, the argument that the JVM code should not be sent with the application or stored with the application because this requires too much bandwidth or storage capacity is becoming less relevant.

  • The changed release cadence of the Java Development Kit would put even more responsibilities for the end users. Many end users find it very annoying to see a popup on their PC asking to upgrade to a newer version of Java. Their applications might be using Java, but this should be transparent to the end user. While different applications might still run on older JVMs, developers often want to use new features in more recent JVMs for their applications. That would lead to even more popups annoying the end user.

  • The concept of application stores, or app stores, changed the mobile landscape and is also gaining traction on the desktop. Using app stores, end users simply decide to install an application using a uniform “click and install” concept. Everything that is required to execute the application is assumed to be part of that application. The end user doesn’t need to manage required components (e.g., a JVM) that are not adding functionality to him.

The Java ecosystem clearly saw this evolution. The JavaFX components used to contain a “javafxpackager” tool that allowed developers to package their JavaFX application, including a Java Virtual Machine and all dependencies, for different platforms (Linux, Windows, Mac OS X).

With the Java Platform Module System, where the JVM itself is broken up into a number of modules, the situation changed. It is now possible to create sub-versions of the JVM that contain only the modules that a specific application needs. Since packaging is relevant to all applications (not just client applications that require a user interface), a new Java packaging tool is part of the JDK since Java 14. We will discuss this tool, called jpackage in the next section.

Apart from jpackage, there is another tool that allows you to bundle your application into a self-contained native application, the GraalVM’s Native Image. With this tool, the application and its dependencies are compiled ahead of time into native applications. We discuss this tool later in this chapter.

The concept of bundling an application with its dependencies and resources, but also with the JVM, is shown in Figure 10-4.
../images/468104_1_En_10_Chapter/468104_1_En_10_Fig4_HTML.jpg
Figure 10-4

Modern development process

The jpackage Tool

What Is jpackage?

Starting with JDK 14, the jpackage tool will be part of the JDK distributions. The specification for jpackage is JEP 343: https://openjdk.java.net/jeps/343.

The summary of this JEP is clear:

Create a new tool for packaging self-contained Java applications.

This summary matches what we discussed earlier in this chapter: we want to create self-contained Java applications, which means that the JVM should be included in the application.

The goals of the JEP, according to the web site, are the following:

Create a simple packaging tool, based on the JavaFX javapackager tool, that
  • Supports native packaging formats to give the end user a natural installation experience. These formats include msi and exe on Windows, pkg and dmg on Mac OS X, and deb and rpm on Linux.

  • Allows launch-time parameters to be specified at packaging time.

  • Can be invoked directly, from the command line, or programmatically, via the ToolProvider API.

From this, it is clear that the jpackage tool builds on top of the JavaFX packaging tool that used to be part of JavaFX 8. Also, the jpackage tool is designed to support existing and common native packaging formats for the popular desktop operating systems. Hence, Java applications created with jpackage can be installed similar to how most other native applications can be installed.

Java client applications now combine two great advantages:
  1. 1.

    Thanks to the Write Once, Run Anywhere paradigm, the code that a developer has to write is really platform independent.

     
  2. 2.

    Thanks to the jpackage tool, although the code itself is platform independent, the deployment procedure is completely in line with a specific platform.

     

Because jpackage is part of the JDK distribution, it is as easy to invoke as the regular java or javac command. The jpackage executable is located in the same directory as the java and javac executables.

When invoked, jpackage will create a native application, based on provided code, dependencies, and resources. It will bundle a Java runtime with the native application, and when the application is executed, the packaged Java runtime will execute the Java code, similar to the case where the runtime was installed separately on the system. This is shown in Figure 10-5.
../images/468104_1_En_10_Chapter/468104_1_En_10_Fig5_HTML.jpg
Figure 10-5

Deployment with jpackage

Using jpackage

To start using the jpackage tool, you have to use Java 14; but, as it will be explained later on, you can bundle your application with other versions of the Java runtime.

With jpackage, these three types of applications are supported:
  • Non-modular applications that run on the classpath, from one or more jar files

  • Modular applications with one or more modular jar files or jmod files

  • Modular applications that have been jlinked into a custom runtime image

Note that, for the two first cases, the tool will run jlink to create a Java runtime for the application and bundle that into the final image. In the third case, the custom runtime you provided will be bundled into the image.

The output of jpackage is a self-contained application image. The image can include the following:
  • Native application launcher (generated by the tool)

  • Application resources (like jar, icns, ico, png)

  • Configuration files (like plist, cfg, properties)

  • Helper libraries for the launcher

  • The Java runtime image, for example, the files that are needed to execute Java bytecode

Although jpackage is part of the same directory that also contains the java and javac commands that are platform independent, using jpackage slightly differs based on the platform you are using. This makes sense, since the output of jpackage is very platform specific. Therefore, jpackage can be considered in the middle of the platform-independent java bytecode and the platform-dependent native executable.

Since jpackage is capable of building an executable for the current platform, you have to run jpackage on all platforms you want to support. Cross-compiling a Java application from one platform into native executables for other platforms is not supported.

Jpackage Usage

The jpackage usage is as follows:
jpackage <options>

Jpackage Options

Tables 10-110-7 show the different options that can be used with jpackage.
Table 10-1

jpackage – generic options

@<filename>

Read options and/or mode from a file.

--package-type <type>

The type of package to create. Valid values are {“exe”, “msi”, “rpm”, “deb”, “pkg”, “dmg”}. If this option is not specified, an application image will be created.

--app-version <version>

Version of the application and/or package.

--copyright <copyright string>

Copyright for the application.

--description <description string>

Description of the application.

--help or -h

Print the usage text with a list and description of each valid option for the current platform to the output stream and exit.

--name or -n <name>

Name of the application and/or package.

--output or -o <output path>

Path where generated output file is placed (absolute path or relative to the current directory).

--temp-root <file path>

Path of a new or empty directory used to create temporary files (absolute path or relative to the current directory). If specified, the temp-root will not be removed upon the task completion and must be removed manually. If not specified, a temporary directory will be created and removed upon the task completion.

--vendor <vendor string>

Vendor of the application.

--verbose

Enables verbose output.

--version

Print the product version to the output stream and exit.

Table 10-2

jpackage – options for creating the runtime image

--add-modules <module name>[,<module name>...]

A comma (“,”)-separated list of modules to add. This module list, along with the main module (if specified), will be passed to jlink as the --add-module argument. If not specified, either just the main module (if --module is specified) or the default set of modules (if --main-jar is specified) are used.

--module-path or -p <module path>...

A :- or ;-separated list of paths. Each path is either a directory of modules or the path to a modular jar. (Each path is absolute or relative to the current directory.)

--runtime-image <file path>

Path of the predefined runtime image that will be copied into the application image (absolute path or relative to the current directory). If --runtime-image is not specified, jpackage will run jlink to create the runtime image using options: --strip-debug, --no-header-files, --no-man-pages, and --strip-native-commands. --bind-services will also be added if --add-modules is not specified.

Table 10-3

jpackage – options for creating the application image

--icon <icon file path>

Path of the icon of the application bundle (absolute path or relative to the current directory).

--input or -i <input path>

Path of the input directory that contains the files to be packaged (absolute path or relative to the current directory). All files in the input directory will be packaged into the application image.

Table 10-4

jpackage – options for creating the application launcher(s)

--add-launcher <file path>

Path to a properties file that contains a list of key-value pairs (absolute path or relative to the current directory). The keys “name” (required), “module,” “add-modules,” “main-jar,” “main-class,” “arguments,” “java-options,” “app-version,” “icon,” and “win-console” can be used. These options are added to, or used to overwrite, the original command line options to build an additional alternative launcher. The main application launcher will be built from the command line options. Additional alternative launchers may be built using this option.

--arguments <main class arguments>

Command line arguments to pass to the main class if no command line arguments are given to the launcher.

--java-options <java options>

Options to pass to the Java runtime.

--main-class <class name>

Qualified name of the application main class to execute. This option can only be used if --main-jar is specified.

--main-jar <main jar file>

The main JAR of the application, containing the main class (specified as a path relative to the input path). Either --module or --main-jar option can be specified but not both.

--module or -m <module name>[/<main class>]

The main module (and optionally main class) of the application. This module must be located on the module path. When this option is specified, the main module will be linked in the Java runtime image. Either --module or --main-jar option can be specified but not both.

Table 10-5

jpackage – platform-dependent options for creating the application launcher

--win-console

Creates a console launcher for the application. Should be specified for the application which requires console interactions.

--mac-bundle-identifier <ID string>

An identifier that uniquely identifies the application for Mac OS X. Defaults to the value of --identifier option. May only use alphanumeric (A-Z,a-z,0-9), hyphen (-), and period (.) characters.

--mac-bundle-name <name string>

Name of the application as it appears in the menu bar. This can be different from the application name. This name must be less than 16 characters long and be suitable for displaying in the menu bar and the application info window. Defaults to the application name.

--mac-bundle-signing-prefix <prefix string>

When signing the application bundle, this value is prefixed to all components that need to be signed that don’t have an existing bundle identifier.

--mac-sign

Request that the bundle be signed.

--mac-signing-keychain <file path>

Path of the keychain to use (absolute path or relative to the current directory). If not specified, the standard keychains are used.

--mac-signing-key-user-name <user name>

Username portion of the typical “Mac Developer ID Application:” signing key.

Table 10-6

jpackage – options for creating the application installer(s)

--app-image <file path>

Location of the predefined application image that is used to build an installable package (absolute path or relative to the current directory). See create-app-image mode options to create the application image.

--file-associations <file path>

Path to a properties file that contains a list of key-value pairs (absolute path or relative to the current directory). The keys “extension,” “mime-type,” “icon,” and “description” can be used to describe the association.

--identifier <id string>

An identifier that uniquely identifies the application. Defaults to the main class name. The value should be a valid DNS name.

--install-dir <file path>

Absolute path of the installation directory of the application on OS X or Linux. Relative sub-path of the installation location of the application such as “Program Files” or “AppData” on Windows.

--license-file <file path>

Path to the license file (absolute path or relative to the current directory).

--resource-dir <path>

Path to override jpackage resources. Icons, template files, and other resources of jpackage can be overridden by adding replacement resources to this directory (absolute path or relative to the current directory).

--runtime-image <file-path>

Path of the predefined runtime image to install (absolute path or relative to the current directory). Option is required when creating a runtime package.

Table 10-7

jpackage – platform-dependent options for creating the application installer(s)

--win-dir-chooser

Adds a dialog to enable the user to choose a directory in which the product is installed.

--win-menu

Adds the application to the system menu.

--win-menu-group <menu group name>

Start Menu group this application is placed in.

--win-per-user-install

Request to perform an install on a per-user basis.

--win-shortcut

Creates a desktop shortcut for the application.

--win-upgrade-uuid <id string>

UUID associated with upgrades for this package.

--linux-bundle-name <bundle name>

Name for Linux bundle. Defaults to the application name.

--linux-deb-maintainer <email address>

Maintainer for .deb bundle.

--linux-menu-group <menu-group-name>

Menu group this application is placed in.

--linux-package-deps

Required packages or capabilities for the application.

--linux-rpm-license-type <type string>

Type of the license (“License:” of the RPM spec).

--linux-app-release <release value>

Release value of the RPM .spec file or DEB control file.

--linux-app-category <category value>

Group value of the RPM .spec file or Section value of the DEB control file.

--linux-deb-copyright-file <file path>

Path to custom copyright file for Debian packaging (absolute path or relative to the current directory).

Requirements

The images created by jpackage are not different from other applications developers create for native platforms. Therefore, the same tools that are used to generate native applications for a specific operating system are used by jpackage as well.

For Windows, there are two additional tools that developers will need to install if they want to generate native packages:
  • exe: Inno Setup, a third-party tool, is required to generate exe installers.

  • msi: Wix, a third-party tool, is required to generate msi installers.

Inno Setup
It can be downloaded from www.​jrsoftware.​org/​isdl.​php. Current version is 6.0.2. Once downloaded, process with the installer, and when finished, add it to the path, running from command line
setx /M PATH "%PATH%;C:Program Files (x86)Inno Setup 6"

Samples

We will now show a few samples that explain how jpackage can be used. The samples themselves are very simple JavaFX applications, and we will not discuss their functionality in this chapter.

Let’s start using jpackage from a terminal window, without build tools or plugins. Since the usage of jpackage is slightly different depending on what platform we are running on, we have to distinguish between using jpackage on Windows, Mac OS, and Linux.

Non-modular Application: Sample1

As a first sample, we explain how to package a Java application that itself is not a module. The application still uses the JavaFX modules, but there is no specific module-info.java in the application itself.

We will now show the instructions on how to package this application into an installer for Windows, Mac, and Linux. The pattern we follow is similar for every platform:
  1. 1.

    Define a number of environment variables.

     
  2. 2.

    Compile the JavaFX application into Java bytecode.

     
  3. 3.

    Run and test the application.

     
  4. 4.

    Create a single jar file, containing the application.

     
  5. 5.

    Create an installer using jpackage.

     
Instructions for Windows

These are the required steps if you want to create an installer for a non-modular application, like this one:

https://github.com/modernclientjava/mcj-samples/tree/master/ch10-packaging/Sample1

Clone the sample and, from a terminal, cd into the root.

These first four steps are not different from regular Java compilation and running.
  1. 1.
    Export these environment variables:
    set JAVA_HOME="C:Users<user>Downloadsjdk-14"
    set PATH_TO_FX="C:Users<user>Downloadsjavafx-sdk-13lib"
    set PATH_TO_FX_MODS="C:Users<user>Downloadsjavafx-jmods-13"

    Note that if you have a different JDK added to the PATH environmental variable, this will take precedence.

     
  2. 2.
    Compile your application:
        dir /s /b src*.java > sources.txt & javac --module-path %PATH_TO_FX% --add-modules javafx.controls,javafx.fxml -d out @sources.txt & del sources.txt
        copy srcorgmodernclientsscene.fxml outorgmodernclients & copy srcorgmodernclientsstyles.css outorgmodernclients
     
  3. 3.
    Run and test:
    java --module-path %PATH_TO_FX% --add-modules javafx.controls,javafx.fxml -cp out org.modernclients.Main
     
  4. 4.
    Create a jar:
    mkdir libs
    jar --create --file=libssample1.jar --main-class=org.modernclients.Main -C out .
     
  5. 5.

    Create the installer.

     
In this step, we use jpackage to create an installer. We showed the different options that can be provided to jpackage in Table 10-1. In the following command, we specify a number of options:
%JAVA_HOME%injpackage --package-type exe -o installer -i libs --main-jar sample1.jar -n Sample1 --module-path %PATH_TO_FX_MODS% --add-modules javafx.controls,javafx.fxml --main-class org.modernclients.Main
As a result, you will get Sample1-1.0.exe (16 MB) that can be distributed, and it just requires a double-click to install the application (Figure 10-6).
../images/468104_1_En_10_Chapter/468104_1_En_10_Fig6_HTML.jpg
Figure 10-6

Sample1 Windows installer

Running the jpackage tool with --verbose shows how the installer is built, where are the default resources stored, and how these can be replaced at your convenience:
Running [C:Program Files (x86)Inno Setup 6iscc.exe, /?]
  Detected [C:Program Files (x86)Inno Setup 6iscc.exe] version [5 ]
"Adding modules: [javafx.controls, javafx.fxml] to runtime image."
jlink arguments: [--output, C:Users<user>AppDataLocalTempjdk.jpackage10243889158246994566imageswin-exe.imageSample1 untime, --module-path, C:\Users\<user>\Downloads\javafx-jmods-12;
C:\Users\<user>\Downloads\jdk-14\jmods, --add-modules, javafx.controls,javafx.fxml, --exclude-files, .*.diz, --strip-native-commands, --strip-debug, --no-man-pages, --no-header-files]
Using default package resource javalogo_white_48.ico [icon]  (add Sample1.ico to the resource-dir to customize)
Using default package resource WinLauncher.template [Template for creating executable properties file.]  (add Sample1.properties to the resource-dir to customize)
setting APPLICATION_IMAGE to C:Users<user>AppDataLocalTempjdk.jpackage10243889158246994566imageswin-exe.imageSample1 for InnoSetup
Using default package resource template.iss [Inno Setup project file]  (add Sample1.iss to the resource-dir to customize)
Using default package resource icon_inno_setup.bmp [setup dialog icon]  (add Sample1-setup-icon.bmp to the resource-dir to customize)
no default package resource  [script to run after application image is populated]  (add Sample1-post-image.wsf to the resource-dir to customize)
Generating EXE for installer to: C:ModernClientsSample1installer
Running [C:Program Files (x86)Inno Setup 6iscc.exe, /q, /oC: ModernClientsSample1installer, C:Users<user>AppDataLocalTempjdk.jpackage10243889158
246994566imageswin-exe.imageSample1.iss] in C:Users<user>AppDataLocalTempjdk.jpackage10243889158246994566imageswin-exe.image
Installer (.exe) saved to: C:ModernClientsSample1installer
Succeeded in building EXE Installer bundle
Modifying the Installer

We can add more options to the jpackage command. For instance, we can add the application to the system menu, to create a desktop shortcut, to let the user choose the installation directory, and to use a custom icon based on the Duke image from

https://hg.openjdk.java.net/duke/duke/raw-file/e71b60779736/3D/Duke%20Waving/openduke.png

Running
$JAVA_HOME/bin/jpackage --installer-type dmg -o installer -i libs --main-jar sample1.jar -n Sample1 --module-path $PATH_TO_FX_MODS --add-modules javafx.controls,javafx.fxml --main-class org.modernclients.Main --win-menu --win-shortcut --win-dir-chooser --icon assetswinopenduke.icns
we’ll get the customized installer shown in Figure 10-7.
../images/468104_1_En_10_Chapter/468104_1_En_10_Fig7_HTML.jpg
Figure 10-7

Installer with custom icon

Mac OS X

These are the required steps if you want to create an installer for a non-modular application, like this one:

https://github.com/modernclientjava/mcj-samples/tree/master/ch10-packaging/Sample1
  1. 1.
    Export these environment variables:
    export JAVA_HOME=/Users/<user>/Downloads/jdk-14.jdk/Contents/Home/
    export PATH_TO_FX=/Users/<user>/Downloads/javafx-sdk-13/lib/
    export PATH_TO_FX_MODS=/Users/<user>/Downloads/javafx-jmods-13/
     
  2. 2.
    Compile your application:
    javac --module-path $PATH_TO_FX --add-modules javafx.controls,javafx.fxml -d out $(find src -name "*.java")
    cp src/org/modernclients/scene.fxml src/org/modernclients/styles.css out/org/modernclients/
     
  3. 3.
    Run and test:
    java --module-path $PATH_TO_FX --add-modules javafx.controls,javafx.fxml -cp out org.modernclients.Main
     
  4. 4.
    Create a jar:
    mkdir libs
    jar --create --file=libs/sample1.jar --main-class=org.modernclients.Main -C out .
     
  5. 5.
    Create the installer:
    $JAVA_HOME/bin/jpackage --package-type dmg -o installer -i libs --main-jar sample1.jar -n Sample1 --module-path $PATH_TO_FX_MODS --add-modules javafx.controls,javafx.fxml --main-class org.modernclients.Main
     
../images/468104_1_En_10_Chapter/468104_1_En_10_Fig8_HTML.jpg
Figure 10-8

Sample1 Mac OS X installer

As a result, you will get Sample1-1.0.dmg (53.6 MB), as shown in Figure 10-8, that can be distributed, and it just requires a double-click to install the application.

Running the jpackage tool with --verbose shows how the installer is built, where are the default resources stored, and how these can be replaced at your convenience:
Building DMG package for Sample1
Building PKG package for Sample1
"Adding modules: [javafx.controls, javafx.fxml] to runtime image."
jlink arguments: [--output, /var/folders/zx/s6qr37tn5l98xzzsxttp9lph0000gn/T/jdk.jpackage8207621724208549423/images/image-9477246125921380963/Sample1.app/Contents/PlugIns/Java.runtime/Contents/Home, --module-path, /Users/<user>/Downloads/javafx-jmods-13:/Users/<user>/Downloads/jdk-14.jdk/Contents/Home/jmods, --add-modules, javafx.controls,javafx.fxml, --exclude-files, .*.diz, --strip-native-commands, --strip-debug, --no-man-pages, --no-header-files]
Using default package resource GenericApp.icns [icon]  (add Sample1.icns to the resource-dir to customize)
Preparing Info.plist: /var/folders/zx/s6qr37tn5l98xzzsxttp9lph0000gn/T/jdk.jpackage8207621724208549423/images/image-9477246125921380963/Sample1.app/Contents/Info.plist
Using default package resource Info-lite.plist.template [Application Info.plist]  (add Info.plist to the resource-dir to customize)
Using default package resource Runtime-Info.plist.template [Java Runtime Info.plist]  (add Runtime-Info.plist to the resource-dir to customize)
Using default package resource background_pkg.png [pkg background image]  (add Sample1-background.png to the resource-dir to customize)
Preparing distribution.dist: /var/folders/zx/s6qr37tn5l98xzzsxttp9lph0000gn/T/jdk.jpackage8207621724208549423/config/distribution.dist
no default package resource  [script to run after application image is populated]  (add Sample1-post-image.sh to the resource-dir to customize)
Running [pkgbuild, --root, /var/folders/zx/s6qr37tn5l98xzzsxttp9lph0000gn/T/jdk.jpackage8207621724208549423/images/image-9477246125921380963, --install-location, /Applications, --analyze, /var/folders/zx/s6qr37tn5l98xzzsxttp9lph0000gn/T/jdk.jpackage8207621724208549423/config/cpl.plist]
pkgbuild: Inferring bundle components from contents of /var/folders/zx/s6qr37tn5l98xzzsxttp9lph0000gn/T/jdk.jpackage8207621724208549423/images/image-9477246125921380963
pkgbuild: Writing new component property list to /var/folders/zx/s6qr37tn5l98xzzsxttp9lph0000gn/T/jdk.jpackage8207621724208549423/config/cpl.plist
Preparing package scripts
Using default package resource preinstall.template [PKG preinstall script]  (add preinstall to the resource-dir to customize)
Using default package resource postinstall.template [PKG postinstall script]  (add postinstall to the resource-dir to customize)
...
Modifying the Installer
We can add a custom icon, based on the Duke image, that can be downloaded from https://hg.openjdk.java.net/duke/duke/file/e71b60779736/3D/Duke%20Waving/openduke.png:
$JAVA_HOME/bin/jpackage --installer-type dmg -o installer -i libs --main-jar sample1.jar -n Sample1 --module-path $PATH_TO_FX_MODS --add-modules javafx.controls,javafx.fxml --main-class org.modernclients.Main --icon assets/mac/openduke.icns
And we get the result shown in Figure 10-9.
../images/468104_1_En_10_Chapter/468104_1_En_10_Fig9_HTML.jpg
Figure 10-9

Customized installer

Linux

These are the required steps if you want to create an installer for a non-modular application, like this one:

https://github.com/modernclientjava/mcj-samples/tree/master/ch10-packaging/Sample1

We show the instructions on how to create an installer for a Debian-based distribution.
  1. 1.
    Export these environment variables:
    export JAVA_HOME=/home/<user>/Downloads/jdk-14/
    export PATH_TO_FX=/home/<user>/Downloads/javafx-sdk-13/lib/
    export PATH_TO_FX_MODS=/Users/<user>/Downloads/javafx-jmods-13/
     
  2. 2.
    Compile your application:
    javac --module-path $PATH_TO_FX --add-modules javafx.controls,javafx.fxml -d out $(find src -name "*.java")
    cp src/org/modernclients/scene.fxml src/org/modernclients/styles.css out/org/modernclients/
     
  3. 3.
    Run and test:
    java --module-path $PATH_TO_FX --add-modules javafx.controls,javafx.fxml -cp out org.modernclients.Main
     
  4. 4.
    Create a jar:
    mkdir libs
    jar --create --file=libs/sample1.jar --main-class=org.modernclients.Main -C out .
     
  5. 5.
    Create the installer:
    $JAVA_HOME/bin/jpackage --package-type deb -o installer -i libs --main-jar sample1.jar -n Sample1 --module-path $PATH_TO_FX_MODS --add-modules javafx.controls,javafx.fxml --main-class org.modernclients.Main
     
The result of this command is a file named sample1-1.0.deb created in a directory named “installer.” Using a file browser to locate this file shows that this is a Debian package indeed (Figure 10-10).
../images/468104_1_En_10_Chapter/468104_1_En_10_Fig10_HTML.jpg
Figure 10-10

Sample1 Linux installer

Modular Application: Sample2

Our second application is a modular application. The source code can be found at https://github.com/modernclientjava/mcj-samples/tree/master/ch10-packaging/Sample2.

It contains a module-info.java file, and the jpackage tool can process this to deal with the modular dependencies. The module-info file is very simple: it declares dependencies on the javafx.controls and javafx.fxml modules, and it exports the module org.modernclients. Also, the latter module is opened to the javafx.fxml module.

The module-info.java file is shown here:
module modernclients {
    requires javafx.controls;
    requires javafx.fxml;
    opens org.modernclients to javafx.fxml;
    exports org.modernclients;
}
Windows
These are the required steps if you want to create an installer for a modular application on Windows:
  1. 1.
    Export these environment variables:
    set JAVA_HOME="C:Users<user>Downloadsjdk-14"
    set PATH_TO_FX="C:Users<user>Downloadsjavafx-sdk-13lib"
    set PATH_TO_FX_MODS="C:Users<user>Downloadsjavafx-jmods-13"
     
  2. 2.
    Compile your application:
    dir /s /b src*.java > sources.txt & javac --module-path %PATH_TO_FX% --add-modules javafx.controls,javafx.fxml -d modsmodernclients @sources.txt & del sources.txt
    copy srcorgmodernclientsscene.fxml modsmodernclientsorgmodernclients & copy srcorgmodernclientsstyles.css modsmodernclientsorgmodernclients
     
  3. 3.
    Run and test:
    java --module-path %PATH_TO_FX%;mods -m modernclients/org.modernclients.Main
     
  4. 4.
    Create custom image:
    %JAVA_HOME%injlink --module-path %PATH_TO_FX_MODS%;mods --add-modules modernclients --output image
     
  5. 5.

    Run and test the image:

     
imageinjava -m modernclients/org.modernclients.Main
  1. 6.
    Create the installer:
    %JAVA_HOME%injpackage --package-type exe -o installer -n Sample2 -m modernclients/org.modernclients.Main --runtime-image image
     

As a result, you will get Sample2-1.0.dmg (19.9 MB) that can be distributed, and it just requires a double-click to install the application.

Mac OS X
These are the required steps if you want to create an installer for a modular application on Mac OS X:
  1. 1.
    Export these environment variables:
    export JAVA_HOME=/Users/<user>/Downloads/jdk-14.jdk/Contents/Home/
    export PATH_TO_FX=/Users/<user>/Downloads/javafx-sdk-13/lib/
    export PATH_TO_FX_MODS=/Users/<user>/Downloads/javafx-jmods-13/
     
  2. 2.
    Compile your application:
    javac --module-path $PATH_TO_FX -d mods/modernclients $(find src -name "*.java")
    cp src/org/modernclients/scene.fxml src/org/modernclients/styles.css mods/modernclients/org/modernclients/
     
  3. 3.
    Run and test:
    java --module-path $PATH_TO_FX:mods -m modernclients/org.modernclients.Main
     
  4. 4.
    Create custom image:
    $JAVA_HOME/bin/jlink --module-path $PATH_TO_FX_MODS:mods --add-modules modernclients --output image
     
  5. 5.

    Run and test the image:

     
image/bin/java -m modernclients/org.modernclients.Main
  1. 6.
    Create the installer:
    $JAVA_HOME/bin/jpackage --package-type dmg -o installer -n Sample2 -m modernclients/org.modernclients.Main --runtime-image image
     

As a result, you will get Sample2-1.0.dmg (65.6 MB) that can be distributed, and it just requires a double-click to install the application.

Linux
These are the required steps if you want to create an installer for a modular application on Linux:
  1. 1.
    Export these environment variables:
    export JAVA_HOME=/home/<user>/Downloads/jdk-14/
    export PATH_TO_FX=/home/<user>/Downloads/javafx-sdk-13/lib/
    export PATH_TO_FX_MODS=/Users/<user>/Downloads/javafx-jmods-13/
     
  2. 2.
    Compile your application:
    javac --module-path $PATH_TO_FX -d mods/modernclients $(find src -name "*.java")
    cp src/org/modernclients/scene.fxml src/org/modernclients/styles.css mods/modernclients/org/modernclients/
     
  3. 3.
    Run and test:
    java --module-path $PATH_TO_FX:mods -m modernclients/org.modernclients.Main
     
  4. 4.
    Create custom image:
    $JAVA_HOME/bin/jlink --module-path $PATH_TO_FX_MODS:mods --add-modules modernclients --output image
     
  5. 5.

    Run and test the image:

     
image/bin/java -m modernclients/org.modernclients.Main
  1. 6.
    Create the installer:
    $JAVA_HOME/bin/jpackage --package-type deb -o installer -n Sample2 -m modernclients/org.modernclients.Main --runtime-image image
     

As a result, you will get Sample2-1.0.deb that can be distributed or installed.

Gradle Projects

The previous samples explained how to use the command line jpackage tool. As with most commands, it often makes sense to use them in existing built tools, for example, Maven or Gradle.

While you can create a task to your build.gradle file, with the required options to run the jpackage tool, there is a plugin that does it already: the org.beryx.jlink plugin, by Serban Iordache (see https://badass-jlink-plugin.beryx.org/).

These are the required steps if you want to create an installer for a modular application with Gradle, like this one:

https://github.com/modernclientjava/mcj-samples/tree/master/ch10-packaging/Sample3
  1. 1.

    Edit the build.gradle and review the required JDK 13 path:

     
plugins {
    id 'application'
    id 'org.openjfx.javafxplugin' version '0.0.8'
    id 'org.beryx.jlink' version '2.15.1'
}
repositories {
    mavenCentral()
}
javafx {
    modules = [ 'javafx.controls', 'javafx.fxml' ]
}
mainClassName = "$moduleName/org.modernclients.Main"
jlink {
    options = ['--strip-debug', '--compress', '2', '--no-header-files', '--no-man-pages']
    launcher {
        name = 'sample3'
    }
    jpackage {
        if (javafx.getPlatform().name() == "OSX") {
            installerType = "dmg"
            jpackageHome = "/Users/<user>/Downloads/jdk-14.jdk/Contents/Home"
        } else if (javafx.getPlatform().name() == "WINDOWS") {
            installerType = "exe"
            jpackageHome = "C:\Users\<user>\Downloads\jdk-14"
            installerOptions = ['--win-menu', '--win-shortcut', '--win-dir-chooser']
        } else if (javafx.getPlatform().name() == "LINUX") {
            installerType = "deb"
            jpackageHome = "/home/<user>/Downloads/jdk-14"
        }
    }
}
  1. 2.
    Run and test:
    ./gradlew run (Mac OS or Linux)
    gradlew run (Windows)
     
  2. 3.
    Create custom image:
    ./gradlew jlink (Mac OS or Linux)
    gradlew jlink (Windows)
     
  3. 4.
    Run and test the image:
    build/image/bin/sample3 (Mac OS or Linux)
    buildimageinsample3 (Windows)
     
  4. 5.
    Create the installer:
    ./gradlew jpackage (Mac or Linux)
    gradlew jpackage (Windows)
     

As a result, you will get Sample3-1.0.dmg (40.6 MB) on Mac OS X or Sample3-1.0.exe (38.6 MB) on Windows or Sample3-1.0.deb on Linux that can be distributed, and it just requires a double-click to install the application.

Using Graal’s Native

Using jpackage, you can build a native application for a specific operating system. The Java runtime is bundled with the application, and when the native application is executed, it will internally use the Java runtime to execute the bytecodes. Typically, the Java runtime contains a Just In Time compiler (JIT) that compiles Java bytecode to native code.

Another option for building a native application moves that compilation step to build time. With GraalVM’s Native Image, the Java code is compiled Ahead Of Time (AOT), which means that the java bytecode is compiled to native code before it is executed and before it is bundled in an application.

As a result, the resulting binary does not contain a Java runtime anymore. This situation is shown in Figure 10-11.
../images/468104_1_En_10_Chapter/468104_1_En_10_Fig11_HTML.jpg
Figure 10-11

Native Image development process

Although the GraalVM project has been active for many years, it only recently became available as a product. It is still in evolution, and parts of it are integrated with the OpenJDK project and vice versa. We recommend to regularly keep an eye on the https://graalvm.org web site and on the GitHub site for the open source code at https://github.com/oracle/graal.

While GraalVM provides the AOT compiler that translates Java bytecode into native code for a given platform, there are more actions needed in order to link the program code into an executable. Fortunately, there already are open source tools available that help developers achieve this. The Gluon Client plugin allows developers to create native images for Linux and Mac OS X (and soon Windows) based on existing Java code.

This same plugin also allows to generate mobile apps, which will be discussed in the next chapter.

We will now show how you can convert a HelloFX application into a native executable.

Requirements

To use the plugin to develop and deploy native applications on Mac OS X platforms, you need a Mac with Mac OS X 10.13.2 or superior and Xcode 9.2 or superior, available from the Mac App Store. Once Xcode is downloaded and installed, open it to accept the license terms.

Once downloaded and installed, you need to set JAVA_HOME pointing to that JDK, like
export JAVA_HOME=/Users/<user>/Downloads/jdk-11.0.2.jdk/Contents/Home

The Code

Listing 10-1 shows the HelloFX main class, and Listing 10-2 shows the styles.css file.
package hellofx;
import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class HelloFX extends Application {
    public void start(Stage stage) {
        String javaVersion = System.getProperty("java.version");
        String javafxVersion = System.getProperty("javafx.version");
        Label label = new Label("Hello, JavaFX " + javafxVersion + ",
           running on Java " + javaVersion + ".");
        ImageView imageView = new ImageView(
           new Image(HelloFX.class.getResourceAsStream("openduke.png")));
        imageView.setFitHeight(200);
        imageView.setPreserveRatio(true);
        VBox root = new VBox(30, imageView, label);
        root.setAlignment(Pos.CENTER);
        Scene scene = new Scene(root, 640, 480);
        scene.getStylesheets().add(
           HelloFX.class.getResource("styles.css").toExternalForm());
        stage.setScene(scene);
        stage.show();
    }
   public static void main(String[] args) {
        launch(args);
    }
}
Listing 10-1

HelloFX.java

.label {
    -fx-text-fill: blue;
}
Listing 10-2

File styles.css

Maven Project

If you have a Java or JavaFX project and you are using Maven as a build tool, you can easily include the plugin to start creating native applications.

The plugin can be found here: https://github.com/gluonhq/client-maven-plugin.

Listing 10-3 shows the pom file for a Maven project.
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>hello</groupId>
    <artifactId>hellofx</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>
    <name>hellofx</name>
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
        <javafx.version>12.0.1</javafx.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.openjfx</groupId>
            <artifactId>javafx-controls</artifactId>
            <version>${javafx.version}</version>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <release>11</release>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.openjfx</groupId>
                <artifactId>javafx-maven-plugin</artifactId>
                <version>0.0.3</version>
                <configuration>
                    <mainClass>hellofx.HelloFX</mainClass>
                </configuration>
            </plugin>
            <plugin>
                <groupId>com.gluonhq</groupId>
                <artifactId>client-maven-plugin</artifactId>
                <version>0.0.12</version>
                <configuration>
                    <mainClass>hellofx.HelloFX</mainClass>
                </configuration>
            </plugin>
        </plugins>
    </build>
    <pluginRepositories>
        <pluginRepository>
            <id>gluon-releases</id>
            <url>
                http://nexus.gluonhq.com/nexus/content/repositories/releases
            </url>
        </pluginRepository>
    </pluginRepositories>
</project>
Listing 10-3

Pom.xml file

Gradle Project

If you have a Java or JavaFX project and you are using Gradle as a build tool, you can easily include the plugin to start creating native applications.

The plugin can be found here: https://github.com/gluonhq/client-gradle-plugin.

Listing 10-4 shows the build.gradle file, and Listing 10-5 shows the settings.gradle file for a Gradle project.
plugins {
    id 'application'
    id 'org.openjfx.javafxplugin' version '0.0.8'
    id 'com.gluonhq.client-gradle-plugin' version '0.0.12'
}
repositories {
    mavenCentral()
}
gluonClient {
}
javafx {
    modules = [ "javafx.controls" ]
}
mainClassName = 'hellofx.HelloFX'
Listing 10-4

File build.gradle

pluginManagement {
    repositories {
        maven {
          url "https://nexus.gluonhq.com/nexus/content/repositories/releases"
        }
        gradlePluginPortal()
    }
}
rootProject.name = 'HelloFX'
Listing 10-5

File settings.gradle

Build the Project

The first step is to build and run the project as a regular Java project (on a regular JVM that you use for your local development, e.g., hotspot).

With Gradle:
    ./gradlew clean build run
With Maven:
    mvn clean javafx:run
The result is shown in Figure 10-12.
../images/468104_1_En_10_Chapter/468104_1_En_10_Fig12_HTML.jpg
Figure 10-12

Running HelloFX on OpenJDK 11.0.2

We will now compile, package, and run the native desktop application.

Compile

Run with Gradle:
    ./gradlew nativeCompile
Or with Maven:
    mvn client:compile
Then wait for a while (depending on your machine, it could easily be 5 minutes or more) until the task finishes successfully. If you check the terminal, you could see the feedback provided during the process, like in Listing 10-6.
> Task :nativeCompile
Omega :: host triplet = x86_64-macos-macos
Omega :: target triplet = x86_64-macos-macos
...
[SUB] [HelloFX:4281]        (cap):   1,321.24 ms
[SUB] [HelloFX:4281]        setup:   3,051.65 ms
[SUB] [HelloFX:4281]   (typeflow):  63,253.17 ms
[SUB] [HelloFX:4281]    (objects):  48,348.30 ms
[SUB] [HelloFX:4281]   (features):   7,441.98 ms
[SUB] [HelloFX:4281]     analysis: 122,221.92 ms
[SUB] [HelloFX:4281]     (clinit):   2,710.24 ms
[SUB] [HelloFX:4281]     universe:   7,039.62 ms
[SUB] [HelloFX:4281]      (parse):   8,027.64 ms
[SUB] [HelloFX:4281]     (inline):  12,303.28 ms
[SUB] [HelloFX:4281]    (compile):  60,476.05 ms
[SUB] [HelloFX:4281]      compile:  86,296.71 ms
[SUB] [HelloFX:4281]        image:  13,426.13 ms
[SUB] [HelloFX:4281]        write:   4,655.63 ms
[SUB] [HelloFX:4281]      [total]: 240,104.69 ms
BUILD SUCCESSFUL in 4m 11s
Listing 10-6

Output during the native compilation phase

As a result, HelloFX.o, with 86.7 MB, can be found under build/client/macos-x86_64/gvm/tmp/SVM-15***/HelloFX.o.

If that is not the case, check for any possible failure in the log files under build/client/macos-x86_64/gvm/log.

Link

Now that the Java code for the application is compiled to native code, we can package the generated code with the required libraries and resources using the nativeLink task .

Run with Gradle:
    ./gradlew nativeLink
Or with Maven:
    mvn client:link
It produces build/client/macos-x86_64/hellofx.app (86.9 MB), as shown in Figure 10-13.
../images/468104_1_En_10_Chapter/468104_1_En_10_Fig13_HTML.jpg
Figure 10-13

HelloFX.app file

Run

Finally, you can run it with Gradle:
    ./gradlew nativeRun
Or with Maven:
    mvn client:run
You should get the output shown in Figure 10-14.
../images/468104_1_En_10_Chapter/468104_1_En_10_Fig14_HTML.jpg
Figure 10-14

Output of HelloFX

Note that you can distribute this native application to any Mac OS X machine and run it directly as any other regular application.

Conclusion

Packaging application code with all required dependencies, Java runtime, and resources is becoming increasingly popular on desktop, mobile, and embedded devices.

The old disadvantages, including larger size and longer download times, are becoming less important due to increasing bandwidth and storage.

The advantages of packaging include much less hassle for the end user (he only needs to install a single application), who can use an approach that is very similar to other applications.

JavaFX applications are regular Java applications, and the emerging packaging tools that exist and are being developed for Java can be used for JavaFX applications as well.

Since these tools are evolving rapidly, we recommend the reader to keep an eye on the samples in our GitHub repository, as they will be updated to the latest version once available.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.191.200.242