183 lines
6 KiB
Bash
183 lines
6 KiB
Bash
# shellcheck shell=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
|
|
}
|