# Sourced by the scripts in scripts/ (and tools/) so every one of them finds # toolchains the same way: from the environment ./install.sh generates. A script calls joeyNeed for each tool it uses, and gets a clear # "install this" message (exit 2, which the gate runners treat as SKIP) # instead of failing halfway through with a missing binary. JOEY_REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" if [ -f "$JOEY_REPO/toolchains/env.sh" ]; then # shellcheck source=/dev/null source "$JOEY_REPO/toolchains/env.sh" fi # joeyNeed VAR INSTALL-TARGET -- exit 2 unless VAR names an existing path. joeyNeed() { local var="$1" local target="$2" if [ -z "${!var:-}" ] || [ ! -e "${!var}" ]; then echo "$(basename "$0"): $var is not available; run: ./install.sh install $target" >&2 exit 2 fi } # joeyNeedFile PATH INSTALL-TARGET -- exit 2 unless PATH exists. joeyNeedFile() { if [ ! -e "$1" ]; then echo "$(basename "$0"): $1 is missing; run: ./install.sh install $2" >&2 exit 2 fi } # DOSBox Staging must never read or write the user's own DOSBox setup, even # ~/.config/dosbox (it creates that tree even with --noprimaryconf). Launch # it as # XDG_CONFIG_HOME="$work/xdg" "$DOSBOX" "${JOEY_DOSBOX_ISOLATE[@]}" ... JOEY_DOSBOX_ISOLATE=(--noprimaryconf --nolocalconf) # joeyExampleProgram NAME PORT [ROOT] -- the built program for a program # name, in ROOT's build/PORT/bin (ROOT: JoeyLib by default, or a project # built on make/project.mk, which lays its build/ out the same way). A # program that ships in its own folder with its DATA (ID_FOLDER) is found # inside that folder. When nothing is built yet it prints the flat path, so # the caller's "not built" message names it. joeyExampleProgram() { local name="$1" local bin="${3:-$JOEY_REPO}/build/$2/bin" local up="${name^^}" local ext="" local candidate case "$2" in iigs) printf '%s\n' "$bin/$up" return 0 ;; amiga) # Mixed-case names. up="${name,,}" up="${up^}" ;; atarist) ext=".PRG" ;; dos) ext=".EXE" ;; x68000) ext=".X" ;; dvx) # Every program is a folder of its own, as it installs in DVX: # apps///, whatever the vendor. up="${up,,}" candidate="$(find "$bin/apps" -mindepth 3 -maxdepth 3 -type f -path "*/$up/$up.app" -print -quit 2>/dev/null)" printf '%s\n' "${candidate:-$bin/apps/joeylib/$up/$up.app}" return 0 ;; *) echo "$(basename "$0"): unknown port '$2' (iigs, amiga, atarist, dos, x68000 or dvx)" >&2 return 2 ;; esac for candidate in "$bin/$up/$up$ext" "$bin/$up$ext"; do if [ -f "$candidate" ]; then printf '%s\n' "$candidate" return 0 fi done # A mixed-case Amiga name (SerTest) is not derivable from the example # name, so take the file in any case. candidate="$(find "$bin" -maxdepth 1 -type f -iname "$up$ext" -print -quit 2>/dev/null)" if [ -n "$candidate" ]; then printf '%s\n' "$candidate" return 0 fi printf '%s\n' "$bin/$up$ext" } # joeyProgramPath PROGRAM PORT [ROOT] -- the built program a gate is given: # PROGRAM itself when it is a path (it holds a /), else the program of that # name (joeyExampleProgram). So a gate runs on a program built anywhere -- # a project's, or a test build outside bin/ -- as well as on an example. joeyProgramPath() { if [[ $1 == */* ]]; then printf '%s\n' "$1" return 0 fi joeyExampleProgram "$@" } # joeyFindWindow DISPLAY PID -- print the X window owned by process PID. # More robust than matching titles, which differ between emulator builds. joeyFindWindow() { local win for win in $(DISPLAY="$1" xdotool search --name -- "." 2>/dev/null); do if [ "$(DISPLAY="$1" xdotool getwindowpid "$win" 2>/dev/null)" = "$2" ]; then printf '%s\n' "$win" return 0 fi done return 1 } # joeyAlive PID -- PID is running. A zombie (exited, not yet reaped) is not, # although `kill -0` still succeeds on it. joeyAlive() { local stat stat="$(ps -o stat= -p "$1" 2>/dev/null)" || return 1 [[ "$stat" != Z* ]] } # joeyChild PID -- print the child of PID: the emulator a `timeout -s KILL` # wrapper started. Stop the child first; stopping only the wrapper can orphan # an emulator that ignores SIGTERM (Hatari does). joeyChild() { local tries local child for ((tries = 0; tries < 20; tries++)); do child="$(pgrep -P "$1" | head -1)" if [ -n "$child" ]; then printf '%s\n' "$child" return 0 fi sleep 0.1 done return 1 } # joeyStop PID... -- stop emulator processes and make sure they are gone. # Hatari, MAME and DOSBox Staging ignore or are slow to act on SIGTERM; a # plain `kill` left them running (and burning CPU) after their script had # finished, so escalate to SIGKILL. Only ever pass PIDs this script started. joeyStop() { local pid local tick for pid in "$@"; do [ -n "$pid" ] || continue kill "$pid" 2>/dev/null || continue for tick in 1 2 3 4 5 6 7 8 9 10; do joeyAlive "$pid" || break sleep 0.2 done kill -9 "$pid" 2>/dev/null || true # Reap it when it is this shell's child (a no-op otherwise), so no # zombie outlives the script where nothing else would reap it. wait "$pid" 2>/dev/null || true done }