Thursday, August 24, 2017

Java Packager and Custom Resources

The Java Packager allows for resources within the app bundle to be replaced by your own custom resources. For example, on macOS the default Info.plist may not work for you. Here is a JDK 9 example that demonstrates replacing the default Info.plist. It's worth noting there is difference between JDK 8 and JDK 9. JDK 8 uses the classpath to search for the resources and JDK 9 requires a new argument. The argument documented here. The documentation about this feature is here:

https://docs.oracle.com/javase/9/deploy/self-contained-application-packaging.htm#JSDPG593

However, note that resource handling is different in the modular world (as I mentioned above about the differences between JDK 8 and JDK 9), and the docs weren't updated (at the time of writing this post, and we didn't catch the bug until recently, sorry). It is no longer possible to load resources via the classpath with the Java Packager. The correct way to do this is with the following ant task so specify the custom resource directory:

<fx:bundleArgument arg="dropinResourcesRoot" value=directory/>

Or if you are using the CLI use the following argument:

-BdropinResourcesRoot=directory

If you add this bundler argument the example in the docs will function the same; The current directory will be where custom resources such as the Info.plist searched for.

Here is the example:


>ls -R

build.xml package src

./package:
macosx

./package/macosx:

Info.plist

./src:

HelloWorld.java

HelloWorld.java

import java.awt.Dimension;
import java.awt.Toolkit;
import javax.swing.BorderFactory;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;

public class HelloWorld {

  private static void createAndShowGUI() {
    //Create and set up the window.
    JFrame frame = new JFrame("Hello World");
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

    Dimension dim = Toolkit.getDefaultToolkit().getScreenSize();
    frame.setBounds(0, 0, dim.width/2, dim.height/2);

    //Add the ubiquitous "Hello World" label.
    JLabel label = new JLabel("Hello World");
    JPanel panel = new JPanel();
    panel.setBorder(BorderFactory.createEmptyBorder(30, 10, 30, 30));
    panel.add(label);

    frame.getContentPane().add(panel);

    //Display the window.
    frame.setVisible(true);
  }

  public static void main(String[] args) {
    //Schedule a job for the event-dispatching thread:
    //creating and showing this application's GUI.
    javax.swing.SwingUtilities.invokeLater(new Runnable() {
      public void run() {
        createAndShowGUI();
      }
    });
  }

}

build.xml:

<project name="Test" default="package"  xmlns:fx="javacom.sun.javafx.tools.ant">

<taskdef resource="com/sun/javafx/tools/ant/antlib.xml"
         uri="javacom.sun.javafx.tools.ant"
         classpath="${java.home}/lib/ant-javafx.jar"/>

<property name="src.dir" value="src"/>
<property name="build.dir" value="build"/>
<property name="classes.dir" value="${build.dir}/hello.world"/>
<property name="bundles.dir" value="output"/>

<target name="clean">
  <delete dir="${build.dir}"/>
  <delete dir="${bundles.dir}"/>
</target>

<target name="compile">
  <mkdir dir="${build.dir}"/>
  <mkdir dir="${classes.dir}"/>
  <javac includeantruntime="false"
         srcdir="${src.dir}"
         destdir="${classes.dir}"/>
  <copy todir="${classes.dir}">
    <fileset dir="${src.dir}">
      <exclude name="**/*.java"/>
    </fileset>
  </copy>
</target>

<target name="jar" depends="compile">
  <mkdir dir="${build.dir}/jars"/>
  <jar destfile="${build.dir}/jars/hello.world.jar" basedir="${build.dir}/hello.world">
    <manifest>
       <attribute name="Main-Class" value="HelloWorld"/>
     </manifest>
  </jar>
</target>

<mkdir dir="${bundles.dir}"/>

<target name="package" depends="jar">
    <fx:deploy outdir="${bundles.dir}"
               outfile="Test"
               nativeBundles="image"
               verbose="true"
               versionCheck="false">

        <fx:application id="Test"
                        name="Test"
                        version="1.0"
                        mainClass="HelloWorld">
        </fx:application>

        <fx:runtime strip-native-commands="false"/>

        <resources>
            <fileset dir="${build.dir}/jars" includes="**/*"/>
        </resources>

        <fx:info title="Test"
                  description="Java Packager Demo"
                  category="Test"
                  copyright="(c) 2017"
                  license="3 Clause BSD">
        </fx:info>

        <fx:bundleArgument arg="mainJar" value="hello.world.jar"/>
        <fx:bundleArgument arg="classpath" value="hello.world.jar"/>
        <fx:bundleArgument arg="win.exe.systemWide" value="true"/>
        <fx:bundleArgument arg="win.menuGroup" value="Games"/>
        <fx:bundleArgument arg="mac.dmg.simple" value="true"/>
        <fx:bundleArgument arg="signBundle" value="false"/>
        <fx:bundleArgument arg="mac.CFBundleName" value="Test"/>
        <fx:bundleArgument arg="dropinResourcesRoot" value="."/>
    </fx:deploy>
</target>

</project>

Now, I'll be honest, I don't like this method of copying custom files into an app bundle, and we have some ideas to simplify the Java Packager and make custom resource handling a lot easier in the future and not so error prone.

No comments:

Post a Comment