Create Java Executable

Create Java Executable

by John Vincent


Posted on May 5, 2018


This document discusses how to Create a Java Executable for MacOS. This discussion will use the Gomoku project as a working example.

Create a Java Executable

There are a number of approaches available. Let's start with some basics.

Logging messages

Review the code, clean out all traces.

May want to review any log4j settings.

May require different log4j configuration files.

Development

src/main/resources/log/log4j.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">

<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/" debug="true">

    <appender name="consoleAppender" class="org.apache.log4j.ConsoleAppender">
        <layout class="org.apache.log4j.PatternLayout">
            <param name="ConversionPattern" value="%m%n" />
        </layout>
    </appender>

    <appender name="fileAppender" class="org.apache.log4j.FileAppender">
        <param name="File" value="/tmp/gomoku.log" />
        <param name="Append" value="true" />
        <layout class="org.apache.log4j.PatternLayout">
            <param name="ConversionPattern" value="[%d{ISO8601}] [%t] %p - %m%n" />
        </layout>
    </appender>

    <root>
        <priority value="info" />   <!-- off, info, debug, error -->
        <appender-ref ref="consoleAppender" />
        <appender-ref ref="fileAppender" />
    </root>

</log4j:configuration>

Also, copy standard log4j.dtd to src/main/resources/log/log4j.dtd

Production

Packaging/log/log4j-production.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">

<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/" debug="true">

    <appender name="consoleAppender" class="org.apache.log4j.ConsoleAppender">
        <layout class="org.apache.log4j.PatternLayout">
            <param name="ConversionPattern" value="%m%n" />
        </layout>
    </appender>

    <appender name="fileAppender" class="org.apache.log4j.FileAppender">
        <param name="Append" value="true" />
        <layout class="org.apache.log4j.PatternLayout">
            <param name="ConversionPattern" value="[%d{ISO8601}] [%t] %p - %m%n" />
        </layout>
    </appender>

    <root>
        <priority value="error" />  <!-- off, info, debug, error -->
        <appender-ref ref="consoleAppender" />
    </root>

</log4j:configuration>

Running Gomoku from Eclipse

Ensure src/main/resources/ is on the Build Path. Ensure log/log4j.xml is in classes. If not, then:

  • Project, Clean

to rebuild.

Running Gomoku from the Command Line

Create script app.command

#!/bin/sh
#
# script to run java app Gomoku
#
PARAMS="$*"
echo "PARAMS are $PARAMS"
#
DEV_HOME=/Users/jv/Desktop/MyDevelopment/github/java/Games/java-gomoku/Gomoku
#
cd $DEV_HOME
#
MYCP=$DEV_HOME/classes:$DEV_HOME/Jars/log4j-1.2.13.jar
#
MY_FILE=$PARAMS
#
java -cp $MYCP -Xdock:name="Gomoku" io.johnvincent.gomoku.App $MY_FILE
#

Notice the classpath which is from where the log4j.xml file will be loaded.

Log4J Configuration

This is a real pain so let's describe the configuration.

Use an Absolute Path

This approach works fine for local development but is of no value for a distributable package.

Shown here for reference

public class App extends JFrame {

        static {
            init();
        }
        private static void init() {
            String log4jConfPath = System.getProperty("user.dir")+File.separator+"log4j.xml";
            System.out.println("log4jConfPath "+log4jConfPath);
            DOMConfigurator.configure(log4jConfPath);
        }

which will load /Users/jv/Desktop/MyDevelopment/github/java/Games/java-gomoku/Gomoku/log4j.xml

Load from the root of the Classpath

If use src/main/resources/log4j.xml, then this file will be loaded by Log4J on startup. This is the simplest configuration as no specialized code is needed to load log4j.xml

Load from non-root of the Classpath

This is my preferred configuration as it allows for better separation of resource files.

To load log4j.xml

public class App extends JFrame {

        static {
            init();
        }
        private static void init() {
            URL url = App.class.getResource("/log/log4j.xml");
            DOMConfigurator.configure(url);
        }

or

        private static void init() {
            URL url = App.class.getClassLoader().getResource("log/log4j.xml");
            DOMConfigurator.configure(url);
        }

Create Packaging files

Need a working directory working

  • mkdir working
  • Add working to .gitignore

The pkg file that will be created is too large for the repository.

  • Add Gomoku.pkg to .gitignore

Create packaging source directory Packaging

  • mkdir -p Packaging/src Packaging/output

Create manifest file Packaging/src/manifest.mf

Manifest-Version: 1.0
Class-path: .
Main-Class: io.johnvincent.gomoku.App

Copied johnvincentio.png icon from the main website. This icon is my corporate identity.

cp {absolute path} Packaging/src/johnvincentio.png

Create Packaging Script

Create script create-package

#!/bin/sh
#
# script to create Gomoku package
#
createJar() (
    echo;
    echo "Creating executable Jar $EXECUTABLE_JAR";
    echo;

    #
    # Copy base files
    #
    echo "Copy base files";
    cp $CUR_DIR/Packaging/src/manifest.mf $JAR_WORK_FILES_DIR;
    
    #
    echo "Copy class files";
    cp -R $CUR_DIR/classes/* $JAR_WORK_FILES_DIR;
    
    #
    # Copy log4j files last as they must overwrite classes
    #
    echo "Copy log4j files";
    cp $CUR_DIR/Packaging/log/log4j-production.xml $JAR_WORK_FILES_DIR/log/log4j.xml;
    
    #
    # Handle referenced Jar files
    #
    (cd $JAR_WORK_FILES_DIR && exec jar xf $CUR_DIR/Jars/log4j-1.2.13.jar);
    
    #
    # clean working directory
    #
    echo;
    echo "Cleaning working directory $JAR_WORK_FILES_DIR";
    (cd $JAR_WORK_FILES_DIR && exec find . -name '.DS_Store' -type f -delete);
    
    #
    # Create an executable Jar
    #
    echo;
    echo "Creating Executable Jar $EXECUTABLE_JAR";
    (cd $JAR_WORK_FILES_DIR && exec jar cmf manifest.mf $EXECUTABLE_JAR .);
)

createImages() (
    echo;
    echo "Creating images in $MACOSX_PACKAGE_FILES_DIR";
    echo;

    #
    # Create Installer background
    #
    echo;
    echo "Creating Installer background in $WORK_IMAGES_DIR";
    sips -z 100 100 -p 150 150 $MACOSX_PACKAGE_FILES_DIR/johnvincentio.png --out $MACOSX_PACKAGE_FILES_DIR/johnvincentio-background.png;
)

createIcons() (
    echo;
    echo "Creating icons in $MACOSX_PACKAGE_FILES_DIR";
    echo;

    #
    # Create Application icon
    #
    echo;
    echo "Creating Application icon in $WORK_DIR/$APP_NAME.iconset";
    
    mkdir $WORK_DIR/$APP_NAME.iconset;
    
    sips -z 128 128 $MACOSX_PACKAGE_FILES_DIR/johnvincentio.png --out $WORK_DIR/$APP_NAME.iconset/icon_128x128.png;
    
    echo;
    echo "Creating Application icon set in $WORK_ICONSET_DIR";
    iconutil --convert icns $WORK_DIR/$APP_NAME.iconset --output $MACOSX_PACKAGE_FILES_DIR/$APP_NAME.icns;
)

createApplication() (
    #
    # MacOS application and installer
    #
    echo;
    echo "Creating MacOS application and installer";
    echo

    javapackager -deploy \
        -vendor johnvincent.io \
        -description $APP_NAME \
        -name $APP_NAME \
        -appclass io.johnvincent.gomoku.App \
        -native pkg \
        -BappVersion=1.0.0 \
        -Bicon=$MACOSX_PACKAGE_FILES_DIR/$APP_NAME.icns \
        -srcdir $PACKAGE_WORK_FILES_DIR \
        -srcfiles $APP_NAME.jar \
        -outdir $WORK_DIR \
        -outfile $APP_NAME
)

CUR_DIR=`pwd`

APP_NAME=Gomoku

WORK_DIR=$CUR_DIR/working/$$
JAR_WORK_FILES_DIR=$WORK_DIR/jar_files

PACKAGE_WORK_FILES_DIR=$WORK_DIR/package_files
MACOSX_PACKAGE_FILES_DIR=$PACKAGE_WORK_FILES_DIR/package/macosx

EXECUTABLE_JAR=$PACKAGE_WORK_FILES_DIR/$APP_NAME.jar

echo
echo "Current directory: $CUR_DIR"
echo "Work directory: $WORK_DIR"
echo "Jar Files directory: $JAR_WORK_FILES_DIR"

# echo "Output directory: $WORK_OUTPUT_DIR"

#
# make directories
#
echo
echo "Creating work directories"
mkdir -p $JAR_WORK_FILES_DIR $MACOSX_PACKAGE_FILES_DIR

#
# create the executable Jar
#
createJar

#
# create images
#
echo
echo "Packaging Files directory: $PACKAGE_WORK_FILES_DIR"
echo "MacOSX directory: $MACOSX_PACKAGE_FILES_DIR"
echo

cp $CUR_DIR/Packaging/src/johnvincentio.png $MACOSX_PACKAGE_FILES_DIR

createImages

#
# create icons
#
createIcons

#
# Build MacOS application and installer
#
createApplication

#
# Copy application files
#
echo
echo "Copy $APP_NAME.jar"

cp $PACKAGE_WORK_FILES_DIR/$APP_NAME.jar $CUR_DIR/$APP_NAME.jar

echo
echo "Copy $APP_NAME.pkg"

cp $WORK_DIR/bundles/$APP_NAME-*.pkg $CUR_DIR/$APP_NAME.pkg

echo "Completed"

Set privileges

chmod 700 create-package

Create Installer

./create-package

which creates

  • Gomoku.jar
  • Gomoku.pkg

Execute the Jar

java -jar Gomoku.jar

Install the Application

open Gomoku.pkg

References

Mac Java

OS X Integration for Java

The Java Packager Tool

Signing Applications for Gatekeeper on MacOS

Signing Your Apps for Gatekeeper

Distribute outside the Mac App Store (macOS)

developer account

Get a developer account on Apple Developer Website

Getting Started

How to use javapackager to build a MacOS application bundle