From 7958276c9f0d230053520ba20f2a00f5ecda18e4 Mon Sep 17 00:00:00 2001 From: salmonik Date: Sun, 1 Nov 2026 15:53:29 +0100 Subject: scripts: rebuild the dev loop around build.sh/run.sh MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `make` did not parse at all (missing separator at line 139), so every make target was dead. The folder also carried three overlapping launchers (boot.sh, qemu.sh, boot-image.sh) plus a fourth partial one in the Makefile, `cargo build --package rsdos` copy-pasted eight times, the QEMU invocation copy-pasted per script, and the test helpers (fail/wait_log/wait_qemu_exit/qemu_hmp) duplicated in five tests. New layout: - scripts/lib.sh: single config (image, OVMF, mem/smp), the one cargo invocation (build_all), the one QEMU shape (qemu_args), and the test harness (test_boot, wait_log, wait_qemu_exit, qemu_hmp, sendkeys) - scripts/build.sh: kernel + every bins/* in one pass - scripts/mksystem.sh: freshness-aware image build that now stages every bins/*.efi into /bin/cli|gui (they were never copied before) - scripts/run.sh: build + stage + boot in one command; --headless, --debug (GDB stub), --monitor (HMP socket), --fresh, --mem/--smp/ --bios/--image; on Wayland it auto-selects XWayland plus grab-on-hover so the mouse can be captured (Ctrl+Alt+G toggles) - scripts/tests/: the five automated tests on the shared harness - deleted: boot.sh, qemu.sh, boot-image.sh, lib-boot-args.sh, build-image-release.sh, build-bootloader.sh (rsboot chain dropped) The Makefile is now a thin wrapper: make run, make test, make system, make inspect, make help. Two-command flow: ./scripts/build.sh then ./scripts/run.sh (run.sh alone also works). Stale test assumptions fixed so the suite is green again: test-gfx waited for "gfx: check" while the shell prints "gfx check: PASS"; test-mouse/test-msg hardcoded msgrecv = task 3 (wm-input took slot 3 — ids are parsed from the log now); the queue-full probe targeted task 0, whose exec pump drains its own queue as completion codes (moved to the cursor task, which never recvs). Verified in QEMU: make test -> test-disk, test-gfx, test-mouse, test-msg, test-fault all PASS. --- scripts/lib.sh | 168 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 168 insertions(+) create mode 100644 scripts/lib.sh (limited to 'scripts/lib.sh') diff --git a/scripts/lib.sh b/scripts/lib.sh new file mode 100644 index 0000000..d5aecac --- /dev/null +++ b/scripts/lib.sh @@ -0,0 +1,168 @@ +#!/usr/bin/env bash +# +# scripts/lib.sh — shared config and helpers for the rs-dos scripts. +# +# Sourced, never executed. Everything here is the single definition of +# "how we build and how we boot": the build script, the image assembler, +# the QEMU runner and the tests all read their settings from this file. +# +# Config is environment-overridable (TARGET, PROFILE, SYSTEM_IMG, OVMF, +# QEMU, MEM, SMP) so callers and CI can tune it without editing scripts. +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" +TESTS_DIR="$SCRIPT_DIR/tests" + +# ---- config --------------------------------------------------------------- +TARGET="${TARGET:-x86_64-unknown-uefi}" +PROFILE="${PROFILE:-release}" +OUT_DIR="${OUT_DIR:-$PROJECT_DIR/target/$TARGET/$PROFILE}" +KERNEL="$OUT_DIR/rsdos.efi" +SYSTEM_IMG="${SYSTEM_IMG:-/tmp/system.img}" +OVMF="${OVMF:-/usr/share/edk2/x64/OVMF.4m.fd}" +QEMU="${QEMU:-qemu-system-x86_64}" +MEM="${MEM:-256M}" +SMP="${SMP:-1}" + +# Binaries whose launch surface is a window rather than a terminal +# (ADR 0006): staged into /bin/gui, everything else into /bin/cli. +GUI_BINS="paint" + +cd "$PROJECT_DIR" + +# ---- build ---------------------------------------------------------------- +# +# The one and only cargo invocation in the repo. Builds the kernel and +# every external binary in a single pass so the image can stage both. + +cargo_flags() { + [ "$PROFILE" = "release" ] && printf '%s' "--release" +} + +build_all() { + echo "=== build (kernel + bins, $PROFILE) ===" + # shellcheck disable=SC2046 # cargo_flags is a flag word, intentionally split + cargo build --workspace --target "$TARGET" $(cargo_flags) +} + +# ---- system image --------------------------------------------------------- + +# stage_image [mksystem args...] — assemble /tmp/system.img if it is stale. +# Freshness is decided inside mksystem.sh; an up-to-date image is never +# touched, so files written at runtime survive. +stage_image() { + "$SCRIPT_DIR/mksystem.sh" "$@" +} + +# ---- QEMU ----------------------------------------------------------------- + +# qemu_args — the common machine every boot shares: OVMF, the system +# image as the single SATA drive, 256M q35. Callers append display, +# monitor and serial choices. +qemu_args() { + printf '%s\n' \ + -bios "$OVMF" \ + -drive "file=$SYSTEM_IMG,format=raw,if=none,id=disk" \ + -device ide-hd,drive=disk,bus=ide.0 \ + -m "$MEM" \ + -machine q35 \ + -cpu qemu64 \ + -no-reboot \ + -smp "$SMP" +} + +# ---- test harness --------------------------------------------------------- +# +# Every test-*.sh does: +# TNAME=disk; source lib.sh; test_boot # waits for the shell +# sendkeys "ls /disk" "poweroff" +# wait_qemu_exit +# Runtime files are namespaced per test so parallel runs cannot collide. + +TNAME="${TNAME:-rsdos}" +QPID="/tmp/rsdos-$TNAME-qpid" +SOCK="/tmp/rsdos-$TNAME-qmon.sock" +LOG="/tmp/rsdos-$TNAME-serial.log" +QLOG="/tmp/rsdos-$TNAME-qemu.log" + +fail() { + echo "FAIL: $1" + [ -f "$LOG" ] && tail -40 "$LOG" + exit 1 +} + +# wait_log FILE PATTERN TIMEOUT-SECONDS — poll a file for a regex. +wait_log() { + local i=0 + while [ "$i" -lt $((${3:-60} * 10)) ]; do + grep -q "$2" "$1" 2>/dev/null && return 0 + sleep 0.1 + i=$((i + 1)) + done + return 1 +} + +# wait_qemu_exit TIMEOUT-SECONDS — poll until the pid is gone. +wait_qemu_exit() { + local i=0 + while [ "$i" -lt $((${1:-30} * 10)) ]; do + kill -0 "$(cat "$QPID" 2>/dev/null)" 2>/dev/null || return 0 + sleep 0.1 + i=$((i + 1)) + done + return 1 +} + +# qemu_hmp CMD — run one HMP command on the monitor socket. +qemu_hmp() { + python3 - "$SOCK" "$1" <<'EOF' +import socket, sys, time +s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) +s.connect(sys.argv[1]) +s.sendall((sys.argv[2] + "\n").encode()) +time.sleep(0.4) +EOF +} + +# sendkeys "line" ... — type shell commands into the guest via sendkeys.py. +sendkeys() { + python3 "$TESTS_DIR/sendkeys.py" --sock "$SOCK" "$@" +} + +# test_cleanup — reap a leftover QEMU from a previous run of this test +# and clear this test's runtime files. Called before test_boot. +test_cleanup() { + kill "$(cat "$QPID" 2>/dev/null)" 2>/dev/null || true + rm -f "$SOCK" "$LOG" +} + +# test_on_exit — EXIT trap: never leave a QEMU behind, but keep the logs +# (they are the post-mortem for a failed test). +test_on_exit() { + kill "$(cat "$QPID" 2>/dev/null)" 2>/dev/null || true +} + +# test_boot [extra-qemu-args...] — boot the system image headless with the +# serial log at $LOG and a monitor socket at $SOCK, then wait for the +# shell. Extra args attach more drives (e.g. a CRASHLOG stick). +test_boot() { + test_cleanup + trap test_on_exit EXIT + # A stale/poisoned OVMF varstore makes the firmware skip the + # \EFI\BOOT fallback entirely — every test boots from a fresh copy. + # Namespaced per test so parallel runs cannot race on it. + local nv="/tmp/rsdos-ovmf-vars-$TNAME.fd" + rm -f "$nv" + cp "$OVMF" "$nv" + local -a qargs + mapfile -t qargs < <(qemu_args) + "$QEMU" -bios "$nv" \ + "${qargs[@]}" \ + -serial "file:$LOG" \ + -monitor "unix:$SOCK,server,nowait" \ + -display none \ + "$@" >"$QLOG" 2>&1 & + echo $! >"$QPID" + wait_log "$LOG" "boot: entering shell" 90 || fail "kernel never reached the shell" +} -- cgit v1.2.3