#! /bin/bash # # Script to start a JMRI @VERSION@ application. # # This script is used for both all POSIX operating systems including Linux # and macOS / OS X application bundles. # # If a "jre" directory is found in the current working directory, # it's assumed that contains a correct JRE or JDK which will be used # regardless of the setting of ${JAVA_HOME} # # If you need to specify an option with spaces in it, escape the spaces with a # leading backslash like "\ ". # # If you need to add any persistent Java options or persistent command line # arguments, include them in the "default_options" statement in the file # jmri.conf in the settings directory. # # The default location for the settings directory is: # - macOS / OS X: ${HOME}/Library/Preferences/JMRI # - all other: ${HOME}/.jmri # # The settings directory can be set to a non-standard location using the # "--settingsdir=/path/to/my/settings" command line option (unlike all other # options, this one cannot be set in the jmri.conf file, since it determines # from where the jmri.conf file is read). # # If your serial ports are not in the default list, include the names in the # command line argument --serial-ports separated by commas: # --serial-ports=locobuffer,cmri # # You can run separate instances of the program with their own preferences # if you # - Provide the name of a configuration file as a parameter # or # - Copy and rename this script. # # If you rename the script to, for example, JmriNew, it will use # "JmriNewConfig.properties" as it's configuration file. Note that configuration # files only determine which profile to use, and if the profile selector should # be shown at application launch. # # If you are getting X11 warnings about meta keys, uncomment the next line # xprop -root -remove _MOTIF_DEFAULT_BINDINGS # # For more information, please see # http://jmri.org/help/en/html/doc/Technical/StartUpScripts.shtml # prevent the use of unbound variables set -u # display valid arguments function usage() { cat </dev/null | grep MaxHeapSize | grep -v SoftMaxHeapSize | awk '{print $4}' ) heap=$( expr ${heap} / 1048576 2>/dev/null ) # bytes to MB # Java heap defaults to 1/4 total memory size # if <= 768MB (1/4 of 3GB), set it ourselves if [ -z "${heap}" ] || [ ${heap} -le 768 ] ; then case "$( uname )" in Linux*) mem=$( cat /proc/meminfo | grep MemTotal | tr -d [:space:][:alpha:]: ) mem=$( expr $mem / 1024 ) ;; Darwin*) mem=$( /usr/sbin/sysctl hw.memsize | tr -d [:alpha:][:space:].: ) mem=$( expr $mem / 1048576 ) ;; *) ;; esac if [ -z "$mem" ] ; then mem=640 fi if [ $mem -le 1024 ] ; then heap=$( expr $mem \* 3 / 4 ) elif [ $mem -le 4096 ] ; then heap=$( expr $mem \* 1 / 2 ) [ $heap -le 768 ] && heap=768 # 3/4 of 1024 else heap=$( expr $mem \* 1 / 4 ) [ $heap -le 2048 ] && heap=2048 # 1/2 of 4096 fi if [ $heap -lt 192 ] ; then heap=192 # 3/4 of 256MB fi fi echo $heap return 0 } # get the script's location as an absolute path SCRIPTDIR=$(cd "$( dirname "${0}" )" && pwd) # define the class to be invoked DEFAULT_APP_NAME="@NAME@" CLASSNAME="@CLASS@" # define empty jmri_options jmri_options="" # define empty array of passed in options declare -a all_options=() # ensure JMRI environment options are always set JMRI_OPTIONS=${JMRI_OPTIONS:-} JMRI_SERIAL_PORTS=${JMRI_SERIAL_PORTS:-} # set default config name CONFIGNAME="" CONFIGFILE="" # Installation locations JAVA_HOME=${JAVA_HOME:-} JMRI_HOME=${JMRI_HOME:-} BUNDLEDIR="" # Set default arguments ARGS= # set DEBUG to any non-empty string to see debugging output DEBUG=${DEBUG:-} # set JEMMY to any non-empty string to configure for Jemmy testing JEMMY=${JEMMY:-} # set default classpaths additions pre_classpath="" post_classpath="" # set the OS (can be overridden in environment for debugging and development) # this value is used to find OS-specific libraries, so it gets normalized # in following case statement OS=${OS:-} if [ -z "${OS}" ] ; then OS=$( uname -s ) fi # sanitize os names case "${OS}" in macosx|Darwin*) OS="macosx" ;; Linux*) OS="linux" ;; esac ARCH=${ARCH:-} settingsdir=${settingsdir:-} # get the settings directory if set on command line found_settingsdir="" for opt in "$@"; do if [ "${found_settingsdir}" = "yes" ]; then # --settingsdir /path/to/... part 2 settingsdir="$opt" jmri_options="${jmri_options} -Djmri.prefsdir=${settingsdir}" break elif [ "$opt" = "--settingsdir" ]; then # --settingsdir /path/to/... part 1 found_settingsdir="yes" elif [[ "$opt" =~ "--settingsdir=" ]]; then # --settingsdir=/path/to/... settingsdir="${opt#*=}" jmri_options="${jmri_options} -Djmri.prefsdir=${settingsdir}" break; fi done if [ -z "$settingsdir" ]; then # set OS-specific settingsdir if not on cmd line case "${OS}" in macosx|Darwin*) settingsdir="${HOME}/Library/Preferences/JMRI" ;; Linux*|*) settingsdir="${HOME}/.jmri" ;; esac fi # log to $settingsdir/log/launcher.log for debugging purposes launcher_log=${settingsdir}/log/launcher.log mkdir -p ${settingsdir}/log [ -f ${launcher_log} ] && rm -f ${launcher_log} # process arguments, stored arguments first, so CLI arguments can override # process default_options from the launcher configuration file if [ -f "${settingsdir}/jmri.conf" ] ; then default_options="" source "${settingsdir}/jmri.conf" if [ -n "${default_options}" ] ; then IFS=' ' read -a all_options <<< "${default_options}" fi fi # process environment and command line arguments if [ -n "${JMRI_OPTIONS}" ] ; then IFS=' ' read -a options <<< "${JMRI_OPTIONS}" for option in "${options[@]}" ; do all_options=("${all_options[@]-}" "${option}") done fi if [ $# -gt 0 ] ; then for option in "$@"; do all_options=("${all_options[@]-}" "${option}") done fi parse_args "${all_options[@]:-}" # define JMRI_HOME if it is not defined if [ -z "${JMRI_HOME}" ] ; then JMRI_HOME="${SCRIPTDIR}" if [ "$OS" = "macosx" ] ; then # on OS X, the default JMRI_HOME is the directory containing the .app bundle BUNDLEDIR=$(cd "${SCRIPTDIR}/../.." && pwd) if [ -f "${BUNDLEDIR}/Contents/MacOS/StartJMRI" ] ; then JMRI_HOME=$(cd "${BUNDLEDIR}/.." && pwd) DEBUG="yes" # default to on, so always available in Console.app else BUNDLEDIR="" fi fi fi cd "${JMRI_HOME}" [ -n "${DEBUG}" ] && echo "PWD: '${PWD}'" | tee -a ${launcher_log} # define JAVA_HOME if needed [ -n "${DEBUG}" ] && echo "initial JAVA_HOME: '${JAVA_HOME}' OS: '${OS}'" | tee -a ${launcher_log} # First, select local java version, if present; this overrides ${JAVA_HOME} passed in if [ -d "${JMRI_HOME}/jre" ]; then JAVA_HOME=${JMRI_HOME}/jre PATH=${JAVA_HOME}/bin:$PATH JAVACMD=java if [ "$OS" = "macosx" ] ; then # if macosx and xattr exists, use it if type "xattr" > /dev/null ; then xattr -r -d com.apple.quarantine ${JAVA_HOME}/bin/java || true fi fi fi # Otherwise, try to find an installed JRE if [ -z "${JAVA_HOME}" ] ; then # # macOS / OS X get special treatment # all others use which to find java # otherwise error out # if [ "$OS" = "macosx" ] ; then # # macOS / OS X has a bewildering array of possibilities these days # # /usr/libexec/java_home will find the correct version, if a JDK for the # required version or newer is installed. # # Look for the Oracle Java 11 JRE and use it if found # Otherwise, use java_home if it's present # Otherwise, prompt the user to install Java # JAVA_HOME="/Library/Internet Plug-Ins/JavaAppletPlugin.plugin/Contents/Home" if [ -x "${JAVA_HOME}/bin/java" ] ; then JAVACMD="${JAVA_HOME}/bin/java" # Test if java is present and at least version 11 java_version="$( "${JAVACMD}" -version 2>&1 | grep version )" [ -n "${DEBUG}" ] && echo "JAVA_HOME/bin/java: '${java_version}'" | tee -a ${launcher_log} if [[ ! "${java_version}" =~ \"21|\"20|\"19|\"18|\"17|\"16|\"15|\"14|\"13|\"12|\"11|\"22|\"23|\"24|\"25|\"26|\"27|\"28|\"29 ]] ; then JAVA_HOME="" fi else # Reset JAVA_HOME since there is no java executable in it JAVA_HOME="" fi # Find an installed JRE/JDK location; note search order for jversion in 21 20 19 18 17 16 15 14 13 12 11 22 23 24 25 26 27 28 29; do if [[ -z "${JAVA_HOME}" && -x /usr/libexec/java_home ]] ; then # Test for any JDKs [ -n "${DEBUG}" ] && echo "test for Java: '${jversion}'" | tee -a ${launcher_log} JAVA_HOME=$( /usr/libexec/java_home --version ${jversion} --failfast 2>/dev/null ) if [ -n "${JAVA_HOME}" ] ; then JAVACMD="${JAVA_HOME}/bin/java" break fi fi done if [ -z "${JAVA_HOME}" ] ; then # JAVA_HOME is still not defined, so prompt to install Java for OS X /usr/bin/osascript << EOT try display alert "To use JMRI you must install a supported Java version, at least Java 11. Java 21 is recommended." \ message "Click \"More Info...\" to learn more about installing Java." \ buttons {"More Info...", "OK"} \ default button "OK" \ cancel button "More Info..." on error number -128 -- user pressed cancel button open location "https://www.jmri.org/java" end try EOT exit 1 fi elif which java >/dev/null 2>&1 ; then JAVACMD=$( which java ) JAVA_HOME="$( dirname ${JAVACMD} )/.." else # we don't know that osascript is available here; macOS shouldn't reach here echo "Please install Java 21 per your operating system vendor's instructions." | tee -a ${launcher_log} exit 1 fi else JAVACMD="${JAVA_HOME}/bin/java" if [ ! -x "${JAVACMD}" ] ; then echo "Unable to execute java using JAVA_HOME=\"${JAVA_HOME}\"." | tee -a ${launcher_log} exit 1 fi fi # make JAVA_HOME available to spawned processes export JAVA_HOME [ -n "${DEBUG}" ] && echo "JAVA_HOME: '${JAVA_HOME}'" | tee -a ${launcher_log} [ -n "${DEBUG}" ] && echo "JAVACMD: '${JAVACMD}'" | tee -a ${launcher_log} # set if -J-Xmx=... is not in options or arguments if [ -z "${jmri_xmx:-}" ] ; then jmri_xmx="-Xmx$( heap_size )m" fi # set if -J-Xms=... is not in options or arguments if [ -z "${jmri_xms:-}" ] ; then # initial heap size = default for 6 GB RAM (default = 1/64 installed RAM) jmri_xms="-Xms96m" fi # permit Java 9 illegal access if [[ $( "${JAVACMD}" -version 2>&1 | grep version ) =~ \"9 ]] ; then jmri_options="${jmri_options} --illegal-access=warn" fi # build library path SYSLIBPATH= LIBDIR="lib" if [ -d "${LIBDIR}/$OS" ] ; then SYSLIBPATH="${LIBDIR}/$OS" fi # one or another of these commands should return a useful value, except that sometimes # it is spelled funny (e,g, amd64, not x86_64). if [ -z "$ARCH" ] ; then for cmd in "arch" "uname -i" "uname -p" "uname -m" ; do ARCH=$( $cmd 2>/dev/null ) if [ -n "$ARCH" ] ; then # canonicalize the architecture names where possible # we currently have AMD64 / X86_64 if [ "$ARCH" = "amd64" ] ; then ARCH="x86_64" fi # and all the flavors of ia32 (traditional x86) if [ "$ARCH" = "i686" -o "$ARCH" = "i586" -o "$ARCH" = "i486" ] ; then ARCH="i386" fi # Now deal with ARM architecture: # armv5 contains v5 soft-float version # armv6l contains v6 hard-float version # armv7l contains v7 hard-float version # aarch64 contains 64-bit v8 version if [ "${ARCH:0:3}" = "arm" ] ; then #determine arm version & hard vs. soft float using readelf if type "readelf" >& /dev/null; then ARCHVERSION=$( readelf -A /proc/self/exe | grep Tag_CPU_arch | cut -d : -f2 ) if [ -z "$ARCHVERSION" -o "${ARCHVERSION:1:2}" = "v5" ] ; then ARCH="armv5" elif [ "${ARCHVERSION:1:2}" = "v6" ] ; then ARCH="armv6l" elif [ "${ARCHVERSION:1:2}" = "v7" ] ; then ARCH="armv7l" else ARCH="armv5" fi fi fi if [ "$ARCH" = "aarch64" -o "$ARCH" = "arm64" ] ; then ARCH="aarch64" fi if [ -d "${SYSLIBPATH}/$ARCH" ] ; then SYSLIBPATH="${SYSLIBPATH}/$ARCH:$SYSLIBPATH" # we're only interested in ONE of these values, so as soon as we find a supported # architecture directory, continue processing and start up the program break fi fi done fi # build classpath dynamically # add pre classpath CP="" if [ -n "${pre_classpath}" ] ; then pre_classpath="${pre_classpath#:}" CP="${pre_classpath}:" fi CP="${CP}.:classes:target/classes:jmri.jar" # add contents of lib CP="${CP}:$( ls -m ${LIBDIR}/*.jar | tr -d ' \n' | tr ',' ':' )" # add contents of the user settings / lib directory, if anything mkdir -p ${settingsdir}/lib if [ ! -z "$(ls -A ${settingsdir}/lib)" ]; then CP="${CP}:$( ls -m ${settingsdir}/lib/*.jar | tr -d ' \n' | tr ',' ':' )" fi # add post classpath if [ -n "${post_classpath}" ] ; then post_classpath="${post_classpath#:}" CP="${CP}:${post_classpath}" fi # remove any "\ " escaped spaces, since these are needed for bash, but not java CP="${CP//\\ / }" [ -n "${DEBUG}" ] && echo "CLASSPATH: '${CP}'" | tee -a ${launcher_log} [ -n "${DEBUG}" ] && echo "Java CMD: '${JAVACMD[@]}'" | tee -a ${launcher_log} # configuration file name is 1st argument. # If not provided, build config file name dynamically if [ "$OS" = "macosx" -a -n "${BUNDLEDIR}" ] ; then # OS X can have spaces in the application name APPNAME=$( basename -s .app "${BUNDLEDIR}" ) else APPNAME=$( basename "$0" ) fi [ -n "${DEBUG}" ] && echo "APPNAME: '${APPNAME}'" | tee -a ${launcher_log} # Process a config file name if passed as an option or when the application is # NOT the default one this script was built for and pass it as a Java property # so that scripts expecting another argument as the first one get that instead if [ "$APPNAME" != "$DEFAULT_APP_NAME" -o -n "${CONFIGNAME}" ] ; then if [ -z "${CONFIGNAME}" ] ; then CONFIGNAME=$( echo $APPNAME | tr -d '[:space:]' | tr -d '=' ) fi CONFIGFILE="-Dorg.jmri.Apps.configFilename=${CONFIGNAME}Config.xml" [ -n "${DEBUG}" ] && echo "CONFIGFILE: '${CONFIGFILE}'" | tee -a ${launcher_log} fi # create the option string # # Add JVM and RMI options to user options, if any if [ "$OS" = "macosx" ] ; then # since bash mangles the application name and icon in $OPTIONS, # these are not stored in $OPTIONS OS_OPTIONS="" # omit custom menu bar if running Jemmy tests if [ "$JEMMY" = "" ] ; then OS_OPTIONS="${OS_OPTIONS} -Dapple.laf.useScreenMenuBar=true" OS_OPTIONS="${OS_OPTIONS} -Dcom.apple.macos.useScreenMenuBar=true" fi OS_OPTIONS="${OS_OPTIONS} -Dfile.encoding=UTF-8" if [ -f "${BUNDLEDIR}/Contents/Resources/@ICON@.icns" ] ; then APPICON="${BUNDLEDIR}/Contents/Resources/@ICON@.icns" else APPICON=$( find "${BUNDLEDIR}" 2>/dev/null | grep -s -m 1 icns ) fi fi OPTIONS="${jmri_options} -noverify" OPTIONS="${OPTIONS} -Djava.security.policy=${LIBDIR}/security.policy" OPTIONS="${OPTIONS} -Djava.rmi.server.codebase=file:target/classes/" OPTIONS="${OPTIONS} -Djava.library.path=.:$SYSLIBPATH:${LIBDIR}" OPTIONS="${OPTIONS} -Dnashorn.args=--no-deprecation-warning" # the next is used by DarkLAF OPTIONS="${OPTIONS} --add-exports java.desktop/sun.awt=ALL-UNNAMED" # memory start and max limits OPTIONS="${OPTIONS} ${OS_OPTIONS-} ${jmri_xms} ${jmri_xmx}" [ -n "${DEBUG}" ] && echo "OPTIONS: '${OPTIONS}'" | tee -a ${launcher_log} # handle ports in --settings argument or JMRI_SERIAL_PORTS environment variable # but not if already in OPTIONS ALTPORTS= if [[ -n "${JMRI_SERIAL_PORTS}" && ! ${OPTIONS} =~ "-Dpurejavacomm.portnamepattern=" ]] ; then ALTPORTS="-Dpurejavacomm.portnamepattern=${JMRI_SERIAL_PORTS}" fi declare -a DOCK_OPTIONS # Apple dock extensions to java get mangled by bash if included in ${OPTIONS} if [ "$OS" = "macosx" ] ; then DOCK_OPTIONS=(-Xdock:name="${APPNAME}" -Xdock:icon="${APPICON}") [ -n "${DEBUG}" ] && echo "DOCK_OPTIONS: '${DOCK_OPTIONS[@]}'" | tee -a ${launcher_log} fi # check java version >= 11 and inform user if not "${JAVACMD}" ${OPTIONS} ${ALTPORTS} ${CONFIGFILE} -cp lib/JavaVersionCheckWindow.jar apps.JavaVersionCheckWindow EXIT_STATUS=$? if [ $EXIT_STATUS -ne 0 ] ; then exit $EXIT_STATUS fi RESTART_CODE=100 EXIT_STATUS=${RESTART_CODE} while [ "${EXIT_STATUS}" -eq "${RESTART_CODE}" ] ; do trap 'kill -TERM $PID' TERM INT trap 'kill -HUP $PID' HUP if [ "$OS" = "macosx" ] ; then "${JAVACMD}" "${DOCK_OPTIONS[@]}" ${OPTIONS} ${ALTPORTS} ${CONFIGFILE} -cp "${CP}" "${CLASSNAME}" ${ARGS} & PID=$! else "${JAVACMD}" ${OPTIONS} ${ALTPORTS} ${CONFIGFILE} -cp "${CP}" "${CLASSNAME}" ${ARGS} & PID=$! fi wait $PID trap - TERM INT HUP wait $PID EXIT_STATUS=$? [ -n "${DEBUG}" ] && echo Exit Status: "${EXIT_STATUS}" | tee -a ${launcher_log} done if [ $EXIT_STATUS -eq 200 ] ; then sudo shutdown -h now fi if [ $EXIT_STATUS -eq 210 ] ; then sudo shutdown -r now fi exit $EXIT_STATUS