0

VSCode, Java 11 JavaFX 18.0.2

I am trying to package my code up for distribution as a desktop app. In my case I want a fully self-contained app because of my target user's profile.

I have been through Jenkov add the Oracle docs here and here which suggest I need ant-javafx.jar. That jar file seems to have been dropped from the standard Java SDK some time around Java 7 and put into the regular JavaFX install lib folder.

It's not there in the build I have.

JavaFX seems to have gone to openjfx.io and nowhere in there can I see support for the ant packaging jar. In fact I see openjfx as a retrograde step as they are increasingly forcing everyone into paid plans (try going round and round the loop of downloading anything that doesn't require an LTS payment).

I have a suspicion that there is some silent assumption that everyone will use something from maven or gradle, and maybe the packaging tools are buried away in one of those build tools. For historical reasons I don't use either and it should be possible to do this packaging without one of them.

So where do I get the JavaFX Ant build tasks from without having to pay someone?

Simon
  • 78,655
  • 25
  • 88
  • 118
  • 2
    See the "Packaging" section in the [tag info](https://stackoverflow.com/tags/javafx/info) – James_D Sep 18 '22 at 18:17
  • 2
    [One of the links](https://openjfx.io/openjfx-docs/#modular) in that tag info section points to a command-line based way to build a custom image with the JavaFX mods and your own module, from OpenJFX. From there you could use [`jpackage`](https://docs.oracle.com/en/java/javase/17/jpackage/packaging-overview.html) to make an installation image, if desired. – James_D Sep 18 '22 at 18:36
  • 2
    The Ant build scripts for JavaFX don't exist anymore, as far as I know. More specifically, the `javafxpackager` tool no longer exists which, if I'm not mistaken, is what was used by those scripts. If possible, upgrade to a more recent version of Java (e.g., 17 LTS) and use `jpackage`. Or look into using GraalVM. If you're stuck on Java 11, then there might be a way to make use of `jlink`. That creates a self-contained application, but everything must be explicit modules. You could then try to find a way to wrap the image with native executables/installers (like what `jpackage` does for you). – Slaw Sep 18 '22 at 20:37
  • 2
    @Slaw As I understand it, `jpackage` will work with Java 11. It’s only included with JDK 14 and later, but you can download a recent JDK and still run `jpackage` against a Java 11 JDK. So even if the OP is stuck with Java 11, `jpackage` is still the way to go. – James_D Sep 18 '22 at 22:16

2 Answers2

1

I have found that the following works as an alternative with Java 19 and OpenJFX 19. I use the maven-dependency-plugin to copy all the dependency jars (excluding JavaFX, which I use as modules from a "full" JDK [one that includes JavaFX)] into the target/lib directory.

#!/bin/bash

set -o errexit
set -o noclobber            
set -o xtrace

# find dependency modules of required modules
DEP_MODS=$(jdeps -quiet --class-path "target/lib/*"  --add-modules java.base,java.logging,java.sql,javafx.controls,javafx.fxml --multi-release base --ignore-missing-deps --print-module-deps target/myapp-4.0-beta.jar)

# create a modular runtime image
jlink --compress=1 --no-header-files --no-man-pages --add-modules "java.logging,java.sql,javafx.controls,javafx.fxml,$DEP_MODS" --output target/myapp-4.0-beta

# Example of running it out of the runtime image
# TEST target/myapp-4.0-beta/bin/java -cp "../../myapp-4.0-beta.jar:../../lib/*" org.myapp.App

# symlink to the artifact jar from the lib directory
$(cd target/lib && ln -s ../myapp-4.0-beta.jar)

# use the lib directory and modular runtime image as input to jpackage
jpackage --input target/lib --runtime-image target/myapp-4.0-beta --main-jar myapp-4.0-beta.jar --main-class org.myapp.App  --type app-image --app-version 4.0 --name app --dest target/dist/bundle --mac-entitlements src/dist/mac/entitlements.plist
guymac
  • 396
  • 3
  • 7
0

It is still possible to invoke the new jlink and jpackage tools from an Ant build file without having to depend on any 3rd party plugins (e.g., Maven, Gradle). I've recently had the need for them in a recent JavaFX project and they're available in this repository.

Here's the build file from that repository (as of writing):

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<project name="javafx-build" default="do-deploy">
    <!-- Application Details -->
    <property name="app.vendor" value="Vendor Name"/>
    <property name="app.title" value="App Name"/>
    <property name="app.version" value="0.0.0"/>
    <property name="app.mainClass" value="app.Main"/>
    <property name="app.outJar" value="out.jar"/>

    <!-- Packaging -->
    <property name="app.package.license" value="LICENSE.txt"/>
    <property name="app.package.icon" value="ICON.ico"/>
    <property name="app.package.flags.win" value="--win-dir-chooser --win-menu --win-per-user-install --win-shortcut"/>
    <property name="app.package.type.win" value="msi"/>
    <property name="app.package.type.unix" value=""/>
    <property name="app.package.type.mac" value=""/>

    <!-- Directories -->
    <property name="dir.sdk.javafx" value="C:\dev\java\jfx20"/>
    <property name="dir.bin" value="../bin"/>
    <property name="dir.dist" value="dist"/>
    <property name="dir.dist.jar" value="${dir.dist}/jar"/>
    <property name="dir.dist.jmods" value="${dir.dist}/jmods"/>
    <property name="dir.dist.runtime" value="${dir.dist}/rt-image"/>
    <property name="dir.dist.package" value="${dir.dist}/package"/>

    <!-- Conditional properties -->
    <condition property="app.package.license.arg"
               value=""
               else="--license-file ${app.package.license}">
        <or>
            <equals arg1="${app.package.license}" arg2=""/>
            <not>
                <isset property="app.package.license"/>
            </not>
       </or>
    </condition>
    <condition property="app.package.icon.arg"
               value=""
               else="--icon ${app.package.icon}">
        <or>
            <equals arg1="${app.package.icon}" arg2=""/>
            <not>
                <isset property="app.package.icon"/>
            </not>
        </or>
    </condition>
    <condition property="app.package.flags.win.arg"
               value=""
               else="${app.package.flags.win}">
        <or>
            <equals arg1="${app.package.flags.win}" arg2=""/>
            <not>
                <isset property="app.package.flags.win"/>
            </not>
            <not>
                <os family="windows"/>
            </not>
        </or>
    </condition>
    <condition property="app.package.type.arg"
               value="--type ${app.package.type.win}">
        <and>
            <not>
                <equals arg1="${app.package.type.win}" arg2=""/>
            </not>
            <os family="windows"/>
       </and>
    </condition>
    <condition property="app.package.type.arg"
               value="--type ${app.package.type.unix}">
        <and>
            <not>
                <equals arg1="${app.package.type.unix}" arg2=""/>
            </not>
            <os family="unix"/>
       </and>
    </condition>
    <condition property="app.package.type.arg"
               value="--type ${app.package.type.mac}">
        <and>
            <not>
                <equals arg1="${app.package.type.mac}" arg2=""/>
            </not>
            <os family="mac"/>
       </and>
    </condition>
    <!-- Fallback -->
    <property name="app.package.type.arg" value=""/>

    <!-- Targets -->
    <target name="do-jar">
        <delete dir="${dir.dist.jar}"/>
        <jar destfile="${dir.dist.jar}/${app.outJar}">
            <manifest>
                <attribute name="Main-Class" value="${app.mainClass}"/>
                <attribute name="Class-Path" value="."/>
                <attribute name="Implementation-Vendor" value="${app.vendor}"/>
                <attribute name="Implementation-Title" value="${app.title}"/>
                <attribute name="Implementation-Version" value="${app.version}"/>
            </manifest>
            <fileset dir="${dir.bin}"/>
        </jar>
    </target>
    <target name="do-staging">
        <copy todir="${dir.dist.jmods}">
            <fileset dir="${dir.sdk.javafx}/jmods">
                <include name="*"/>
            </fileset>
        </copy>
        <delete dir="${dir.dist.runtime}"/>
        <exec executable="jlink" failonerror="true">
            <arg line="--strip-native-commands"/>
            <arg line="--no-header-files"/>
            <arg line="--no-man-pages"/>
            <arg line="--compress=2"/>
            <arg line="--strip-debug"/>
            <arg line="--module-path ${dir.dist.jmods}"/>
            <arg line="--add-modules ALL-MODULE-PATH"/>
            <arg line="--output ${dir.dist.runtime}"/>
        </exec>
    </target>
    <target name="do-package">
        <delete dir="${dir.dist.package}"/>
        <exec executable="jpackage" failonerror="true">
            <arg line="${app.package.type.arg}"/>
            <arg line="--dest ${dir.dist.package}"/>
            <arg line="--input ${dir.dist.jar}"/>
            <arg line="--main-class ${app.mainClass}"/>
            <arg line="--main-jar ${app.outJar}"/>
            <arg line="--runtime-image ${dir.dist.runtime}"/>
            <arg line="--name &quot;${app.title}&quot;"/>
            <arg line="--app-version &quot;${app.version}&quot;"/>
            <arg line="--vendor &quot;${app.vendor}&quot;"/>
            <arg line="${app.package.flags.win.arg}"/>
            <arg line="${app.package.license.arg}"/>
            <arg line="${app.package.icon.arg}"/>
        </exec>
    </target>
    <target name="do-deploy" depends="do-jar, do-staging, do-package">
    </target>
</project>

A few things to note: this expects that the program's compiled code (*.class) is in the ../bin directory and uses JavaFX jmods, assuming that they're in the directory you've specified for the dir.sdk.javafx property (but you're free to modify all of this to your liking).