The Shell Script Launcher, In Detail


I recently re-worked the shell script launcher for Big Block Engine, so I wanted to share it and explain what it does in general.  You'll find the complete file at the end of this post, in a code block.  The script itself is released as public domain, so if it's useful to you, do as you please with it.  There's no need to give credit, but a link to Big Block Engine would make me happy.

As any Java developer knows, getting your application to run the way you want on multiple platforms can sometimes be a pain, especially when you factor in users that don't know what they're doing.  I've seen a great many solutions to this problem, including shell scripts for Linux, batch files on Windows and custom EXE launchers, also for Windows.  I find that many fall short of the actual goal of starting their bundled application in some way or another.  Just the other day, I spent roughly an hour or two fighting to get a very old java program running on a modern system, because it's shell script just didn't work and it's main jar file wasn't built to be runnable.

The shell script here is the one I made for this onerous job.

What this script does:

  • Detecting a Java Virtual Machine (JVM)
    • Bundled JVM is checked for first
    • JVM found via 'JAVA_HOME' environment variable
    • JVM found via the 'PATH' environment variable, if all else fails
  • Checking the version of the JVM, for compatibility's sake
  • Checking for an X Server 'DISPLAY' environment variable
  • Does one of the following:
    • Launch the game via it's main jar file
    • Inform the user of any failures
      • Via the terminal, if there is one
      • Via a simple dialog box, if there is no terminal and an X Server is available

Optionally, you can modify the script slightly and use the 'cat' command to combine the script together with the main jar, to make a runnable jar file into a executable!  This is described in the script itself as using the jar as a binary payload.

It also determines if the script is running on a terminal and sets a property that my game engine pays attention to to determine whether it should displays it's log via a window or not, but this is specific to my game engine.

What this script DOES NOT do:

  • Prepare and use a 'CLASSPATH' variable for launching an application
  • Launch an application via unpackaged, naked class files
  • Launch an application by knowing the name of it's main class

All of the above things it doesn't do are the job of the application's main Jar file and should be setup via it's manifest file, by the developer.  Most of the troubles I've seen people have with launching Java applications were because the developer that wrote the application didn't understand that a jar file does all this work, if properly prepared.  Heck, if a jar file is properly prepared, under Windows, you can double click the jar file and it will just start!  The manifest is just a text file dropped into the Jar file along with the class files!  It tells Java the name of the main class and can even be used to setup the class-path for the application.  Library Jar files also have a manifest, which can add additional Jar files to the class-path.  There really is nothing special about a Jar manifest, aside from correct formatting, which is easy.

Sorry if that last paragraph was a bit of a rant.  It's been an annoyance to me for about 18 years and sadly, I don't see it changing any time soon.

In conclusion, I hope this script will make the job of other Java developers easier.  I hope it will also make the lives of end-users easier.

This takes care of the Linux side of things, providing an easy and reliable script to launch a java application.  If you want a simple approach to launching Java applications for Windows, I suggest Launch4j, which is what I've used for this project to provide EXE launchers, since that's what the average Windows user will be expecting.

I am looking for something similar to make a launcher for the Mac OS, but I've never owned a Mac and couldn't test it even if I knew what to do.  If you have some idea of how this kind of thing is done on a Mac, please comment and let me know.

#!/bin/bash
# This shell script is released into the Public Domain; if it does you any good,
# feel free to use and/or modify it
# This script does a bunch of sanity checking and then launches the target JAR
# file for a Java application
# If the environment is not sane, it gives some useful feedback on the problem,
# using Zenity to display a dialog box, if available and not run from a terminal
# The main purpose of this is to notify a user when Java isn't installed or
# likely isn't installed correctly; there's also a check for an X server, for
# GUI applications to fail early on with a decent error message, instead of
# throwing an ugly HeadlessException
# Change directory to the location of this script, to avoid issues with relative
# paths to files referenced here
cd "${0%/*}"
# Some constants and variables for later
# Version Integer 4 = Java 1.4, 5 = Java 5, 6 = Java 6, etc.
TARGET_VER=8
# The JAR file to launch, relative to the directory of this script
# make this "$0" to execute a binary payload (on this script) as a JAR
TARGET_JAR="bigblockengine.jar"
#TARGET_JAR="$0"
# Name of the application to display in error dialog
TARGET_NAME="Big Block Engine"
# This is a directory to search for a JVM, for the sake of bundling one with the
# application
JAVA_DIR="jvm"
# Any required options for the JVM itself go here
JAVA_OPTIONS=""
#JAVA_OPTIONS="-Xmx1024M"
# Any options you want passed to the application, before command-line parameters
# are passed
APPLICATION_OPTIONS=""
# Some strings to display for various error conditions
TOO_OLD="Java is installed, but is too old."
NO_JAVA_PATH="The Java executable has not been found on directories indicated by the 'PATH' variable.\nThis may indicate that Java isn't installed, or it may indicate that your 'PATH' variable does not include it."
PLEASE_INSTALL="Please install Java 8 or higher."
NO_X="No 'DISPLAY' variable set.\nThis application uses a GUI and cannot run without an X server."
DOWNLOAD_URL="    https://adoptopenjdk.net/\n    https://www.azul.com/downloads/zulu-community/\n    http://java.com/en/download/"
DOWNLOAD_MESSAGE="You can download Java from any of the following:\n$DOWNLOAD_URL"
ERROR_TITLE="Unable to Start '${TARGET_NAME}'"
# This checks the supplied 'java' command, to see if it exists, then checks it's
# version against the required version, displaying 'good' if all is correct,
# 'bad' if the file doesn't exist or isn't executaable and 'old' if the java
# version is too old
#  Argument 1: Path to the target 'java' executable
#  Argument 2: Integer version number, as matching the comment for the
#   TARGET_VER variable, above
function check-java()
{
 if ! which "$1" >/dev/null ; then
 echo 'bad'
 elif [ $("$1" -version 2>&1 | head -1 | cut -d'"' -f2 | sed '/^1\./s///' | cut -d'.' -f1) -lt "$2" ]; then
 echo 'old'
 else
 echo 'good'
 fi
}
# Any of the checks below may switch this to false, forcing the script to
# terminate with an error message
RUN_TARGET=true
# If this becomes true, this indicates that we found at least one JVM that was
# too old and if we never find a good JVM at all, then this should be reported
# to the user
JAVA_OLD=false
# Check if we're running from a terminal of some kind
ON_TERMINAL=true
if ! [ -t 0 ] ; then
 # This one is specific to Big Block Engine
 # Optionally add the command-line switch to turn on the property for the GUI
 # log 
 JAVA_OPTIONS="$JAVA_OPTIONS -Dnet.sf.simulatoralive.blockgame.GUIConsole=true"
 
 # We need this information later, for displaying dialog boxes instead of
 # unhelpfully displaying messages to Standard Out
 ON_TERMINAL=false
fi
# Check for Zenity; don't bother with dialog boxes without it
if ! which zenity >/dev/null ; then
 ON_TERMINAL=true
fi
# Check for a proper DISPLAY variable, to avoid using zenity if no X
ON_X=true
if ! [ -n "$DISPLAY" ]; then
 ON_TERMINAL=true
 ON_X=false
fi
# Check for java in a directory specifically set aside for a JVM
if [ -d "$JAVA_DIR" ]; then
 JAVA_LAUNCHER="$JAVA_DIR/bin/java"
 JAVA_GOOD=$( check-java "$JAVA_LAUNCHER" $TARGET_VER )
# echo $JAVA_GOOD : $JAVA_LAUNCHER
 if ! [ "$JAVA_GOOD" = 'good' ]; then
 RUN_TARGET=false
 fi
 if [ "$JAVA_GOOD" = 'old' ]; then
 JAVA_OLD=true
 fi
else
 RUN_TARGET=false
fi
# Check for java, based on JAVA_HOME environment variable, which is a sort of
# de-facto standard for locating java on some systems
if [ "$RUN_TARGET" = false ] && [ -d "$JAVA_HOME" ]; then
 JAVA_LAUNCHER="$JAVA_HOME/bin/java"
 JAVA_GOOD=$( check-java "$JAVA_LAUNCHER" $TARGET_VER )
# echo $JAVA_GOOD : $JAVA_LAUNCHER
 if ! [ "$JAVA_GOOD" = 'good' ]; then
 RUN_TARGET=false
 fi
 if [ "$JAVA_GOOD" = 'old' ]; then
 JAVA_OLD=true
 fi
else
 RUN_TARGET=false
fi
# Check for java on the PATH, but only if the above failed to find a good JVM
if [ "$RUN_TARGET" = false ]; then
 JAVA_LAUNCHER="java"
 JAVA_GOOD=$( check-java "$JAVA_LAUNCHER" $TARGET_VER )
# echo $JAVA_GOOD : $JAVA_LAUNCHER
 if [ "$JAVA_GOOD" = 'good' ]; then
 RUN_TARGET=true
 else
 RUN_TARGET=false
 fi
 if [ "$JAVA_GOOD" = 'old' ]; then
 JAVA_OLD=true
 fi
fi
# Some debug code
#echo "JAVA_LAUNCHER=$JAVA_LAUNCHER"
#echo "RUN_TARGET=$RUN_TARGET"
#echo "JAVA_OLD=$JAVA_OLD"
# This final check is for GUI applications that need an X server
# Comment out if you don't need this
# Check for a running X server
if ! [ "$ON_X" = true ]; then
 echo -e "$NO_X" >&2
 RUN_TARGET=false
fi
# Finally, all sanity checks are done: Start the program, using passed
#  command-line arguments!
if [ "$RUN_TARGET" = true ]; then
 "$JAVA_LAUNCHER" $JAVA_OPTIONS -jar "$TARGET_JAR" $APPLICATION_OPTIONS "$@"
# Or time to call Zenity to display the error dialog and exit with error code
else
 # Construct the final error message piece by piece
 ERROR_TEXT="The following error(s) have occured:"
 if [ "$JAVA_OLD" = true ]; then
 ERROR_TEXT="$ERROR_TEXT\n$TOO_OLD"
 else
 ERROR_TEXT="$ERROR_TEXT\n$NO_JAVA_PATH"
 fi
 ERROR_TEXT="$ERROR_TEXT\n$PLEASE_INSTALL\n$DOWNLOAD_MESSAGE"
 
 if [ "$ON_TERMINAL" = true ]; then
 echo -e "$ERROR_TITLE\n"
 echo -e "$ERROR_TEXT"
 else
 zenity --error --width=400 --title="$ERROR_TITLE" --text="$ERROR_TEXT"
 fi
 # Exit with error code
 exit 1
fi
# Prevent a binary payload from being executed as part of this script
# Also, a couple blank lines to make sure the payload doesn't interfere with the
# exit command; I made that mistake the first time I tested a payload
exit
# And this will ensure editors don't snip off the trailing blank lines

Get Big Block Engine

Download NowName your own price

Leave a comment

Log in with itch.io to leave a comment.