joeylib2/scripts/lib/joeyEnv.sh
2026-10-13 14:01:46 -05:00

182 lines
6 KiB
Bash

# Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
# See LICENSE.md for license information.
# SPDX-License-Identifier: MIT
#
# 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
;;
psp)
# Every program is a folder of its own, as PSP/GAME/ holds it.
printf '%s\n' "$bin/$up/EBOOT.PBP"
return 0
;;
amiga)
# Mixed-case names.
up="${name,,}"
up="${up^}"
;;
atarist|ste) ext=".PRG" ;;
dos) ext=".EXE" ;;
x68000) ext=".X" ;;
sdl3|haiku|haiku32|beos) ext="" ;;
web) ext=".html" ;;
win32|win16) ext=".EXE" ;;
dvx)
# Every program is a folder of its own, as it installs in DVX:
# apps/<vendor>/<name>/, 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, ste, dos, x68000, dvx, sdl3, web, win32, win16, psp, haiku, haiku32 or beos)" >&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
}