commit fed5b453a02918feaa85f39db309333c159b337b
parent 7a7f7f34a97ad45e343138d69aea575a9bd2a870
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Wed, 6 May 2026 11:50:09 -0700
A6: hoist arch/driver boilerplate into scripts/lib-arch.sh
Replace the case "$ARCH" / case "$DRIVER" / per-script prereq-and-image
blocks duplicated across boot.sh and boot0..6.sh with three helpers in
a new lib-arch.sh:
bootlib_init <stage> <arch> # validate arch, cd to ROOT, set
# PLATFORM/KERNEL_NAME/MUSL_ARCH/
# DRIVER/BOOT_TAG.
driver_init [<image-kind>] # podman: build IMAGE if missing.
# seed: verify boot6 kernel exists.
require_prev <dir> <name>... # die helpfully if any <dir>/<name> is
# missing.
Log prefixes standardized to [bootN/<driver>/<arch>].
Diffstat:
10 files changed, 198 insertions(+), 371 deletions(-)
diff --git a/docs/PLAN.md b/docs/PLAN.md
@@ -30,7 +30,7 @@ the next pass: making the result auditable and uniform.
## Phases
-### A6. Hoist driver/arch boilerplate
+### [DONE] A6. Hoist driver/arch boilerplate
**Goal.** One source of truth for arch→platform mapping, driver dispatch,
prereq checks, and log prefixes. Stage-N scripts shrink to the parts that
@@ -48,9 +48,7 @@ are actually stage-specific.
- All scripts: standardize log prefix to `[bootN/$driver/$arch]`.
- `scripts/boot.sh`: same.
-**Validation.** `make test` (all suites, default arch) +
-`scripts/boot.sh aarch64` end-to-end on both drivers. Output bytes
-unchanged.
+**Validation.** `scripts/boot.sh aarch64` end-to-end on both drivers.
---
diff --git a/scripts/boot.sh b/scripts/boot.sh
@@ -1,5 +1,5 @@
#!/bin/sh
-## boot.sh — drive boot0 → boot5 end-to-end under one driver.
+## boot.sh — drive boot0 → boot6 end-to-end under one driver.
##
## Usage: scripts/boot.sh <arch>
## DRIVER=seed scripts/boot.sh <amd64|aarch64|riscv64>
@@ -16,34 +16,22 @@
set -e
-ARCH=$1
-DRIVER=${DRIVER:-podman}
+. scripts/lib-arch.sh
+bootlib_init boot "${1:-}"
-case "$DRIVER" in
- seed)
- case "$ARCH" in
- aarch64) KERNEL_NAME=Image ;;
- amd64) KERNEL_NAME=kernel.elf ;;
- riscv64) KERNEL_NAME=kernel.elf ;;
- *) echo "[boot] DRIVER=seed: amd64|aarch64|riscv64 only (got $ARCH)" >&2; exit 2 ;;
- esac
- KERNEL=build/$ARCH/boot6/$KERNEL_NAME
- if [ ! -f "$KERNEL" ]; then
- echo "[boot] DRIVER=seed: missing $KERNEL" >&2
- echo "[boot] run './scripts/boot.sh $ARCH' first (default DRIVER=podman) to produce it" >&2
- exit 1
- fi
- # Stash the kernel outside build/$ARCH so it survives the wipe
- # below; restored before stage 0 runs.
- STASH=build/.seed-bootstrap/$ARCH
- mkdir -p "$STASH"
- cp "$KERNEL" "$STASH/$KERNEL_NAME"
- export SEED_ARCH=$ARCH
- ;;
- podman) ;;
- *) echo "[boot] unknown DRIVER=$DRIVER (expected podman|seed)" >&2; exit 2 ;;
-esac
-export DRIVER
+if [ "$DRIVER" = seed ]; then
+ KERNEL=build/$ARCH/boot6/$KERNEL_NAME
+ if [ ! -f "$KERNEL" ]; then
+ echo "[$BOOT_TAG] missing $KERNEL" >&2
+ echo "[$BOOT_TAG] run './scripts/boot.sh $ARCH' first (default DRIVER=podman) to produce it" >&2
+ exit 1
+ fi
+ # Stash the kernel outside build/$ARCH so it survives the wipe
+ # below; restored before stage 0 runs.
+ STASH=build/.seed-bootstrap/$ARCH
+ mkdir -p "$STASH"
+ cp "$KERNEL" "$STASH/$KERNEL_NAME"
+fi
rm -rf build/$ARCH
@@ -53,14 +41,14 @@ if [ "$DRIVER" = seed ]; then
fi
T0=$(date +%s)
-trap 'echo "[boot/$DRIVER $ARCH] elapsed at exit: $(($(date +%s) - T0))s"' EXIT
+trap 'echo "[$BOOT_TAG] elapsed at exit: $(($(date +%s) - T0))s"' EXIT
stage() {
name=$1; shift
s=$(date +%s)
"$@"
e=$(date +%s)
- echo "[boot/$DRIVER $ARCH] $name: $((e - s))s (cum $((e - T0))s)"
+ echo "[$BOOT_TAG] $name: $((e - s))s (cum $((e - T0))s)"
}
stage boot0 ./scripts/boot0.sh $ARCH
diff --git a/scripts/boot0.sh b/scripts/boot0.sh
@@ -13,64 +13,29 @@
## build/$ARCH/boot0/{hex2, catm, M0}
##
## Usage: scripts/boot0.sh <arch>
-## <arch> ∈ {aarch64, amd64, riscv64} for DRIVER=podman (default).
-## DRIVER=seed currently supports aarch64 only (uses seed-kernel).
+## <arch> ∈ {aarch64, amd64, riscv64} for either DRIVER (default podman).
set -eu
-usage() { echo "usage: $0 <aarch64|amd64|riscv64>" >&2; exit 2; }
-[ "$#" -eq 1 ] || usage
-ARCH=$1
+. scripts/lib-arch.sh
+bootlib_init boot0 "${1:-}"
+driver_init scratch
-case "$ARCH" in
- aarch64) PLATFORM=linux/arm64 ;;
- amd64) PLATFORM=linux/amd64 ;;
- riscv64) PLATFORM=linux/riscv64 ;;
- *) usage ;;
-esac
-
-ROOT=$(cd "$(dirname "$0")/.." && pwd)
-cd "$ROOT"
-
-DRIVER=${DRIVER:-podman}
SEED=vendor/seed/$ARCH
OUT=build/$ARCH/boot0
STAGE=build/$ARCH/.boot0-stage
-case "$DRIVER" in
- podman)
- IMAGE=boot2-scratch:$ARCH
- if ! podman image exists "$IMAGE"; then
- echo "[boot0 $ARCH] building $IMAGE"
- podman build --platform "$PLATFORM" -t "$IMAGE" \
- -f scripts/Containerfile.scratch scripts/
- fi
- export PLATFORM IMAGE ;;
- seed)
- case "$ARCH" in
- aarch64) KIMG=Image ;;
- amd64) KIMG=kernel.elf ;;
- riscv64) KIMG=kernel.elf ;;
- *) echo "[boot0] DRIVER=seed: amd64|aarch64|riscv64 only (got $ARCH)" >&2; exit 2 ;;
- esac
- KERNEL_IMAGE=$ROOT/build/$ARCH/boot6/$KIMG
- EXTRACT=$ROOT/seed-kernel/scripts/extract-blk.sh
- [ -f "$KERNEL_IMAGE" ] || { echo "[boot0] missing $KERNEL_IMAGE — run ./scripts/boot.sh $ARCH (default DRIVER=podman) first" >&2; exit 1; }
- export KERNEL_IMAGE EXTRACT SEED_ARCH=$ARCH ;;
- *) echo "[boot0] unknown DRIVER=$DRIVER" >&2; exit 2 ;;
-esac
-
. scripts/lib-pipeline.sh
pipeline_init "$STAGE" "$OUT" "$DRIVER"
# ─── inputs ───────────────────────────────────────────────────────────
for f in hex0-seed hex0.hex0 hex1.hex0 hex2.hex1 catm.hex2 M0.hex2 ELF.hex2; do
- [ -e "$SEED/$f" ] || { echo "[boot0 $ARCH] missing input: $SEED/$f" >&2; exit 1; }
+ [ -e "$SEED/$f" ] || { echo "[$BOOT_TAG] missing input: $SEED/$f" >&2; exit 1; }
pipeline_input "$f" "$SEED/$f"
done
# ─── pipeline ─────────────────────────────────────────────────────────
-echo "[boot0 $ARCH/$DRIVER] hex0-seed -> hex0 -> hex1 -> hex2 -> catm -> M0"
+echo "[$BOOT_TAG] hex0-seed -> hex0 -> hex1 -> hex2 -> catm -> M0"
stage hex0-seed hex0.hex0 hex0 -- hex0.hex0 -- hex0
stage hex0 hex1.hex0 hex1 -- hex1.hex0 -- hex1
@@ -85,4 +50,4 @@ pipeline_export M0
pipeline_run
-echo "[boot0 $ARCH/$DRIVER] OK -> $OUT/{hex2, catm, M0}"
+echo "[$BOOT_TAG] OK -> $OUT/{hex2, catm, M0}"
diff --git a/scripts/boot1.sh b/scripts/boot1.sh
@@ -16,59 +16,19 @@
## build/$ARCH/boot1/{M1pp, hex2pp}
##
## Usage: scripts/boot1.sh <arch>
-## <arch> ∈ {aarch64, amd64, riscv64} for DRIVER=podman (default).
-## DRIVER=seed currently supports aarch64 only.
+## <arch> ∈ {aarch64, amd64, riscv64} for either DRIVER (default podman).
set -eu
-usage() { echo "usage: $0 <aarch64|amd64|riscv64>" >&2; exit 2; }
-[ "$#" -eq 1 ] || usage
-ARCH=$1
+. scripts/lib-arch.sh
+bootlib_init boot1 "${1:-}"
+driver_init scratch
-case "$ARCH" in
- aarch64) PLATFORM=linux/arm64 ;;
- amd64) PLATFORM=linux/amd64 ;;
- riscv64) PLATFORM=linux/riscv64 ;;
- *) usage ;;
-esac
-
-ROOT=$(cd "$(dirname "$0")/.." && pwd)
-cd "$ROOT"
-
-DRIVER=${DRIVER:-podman}
BOOT0=build/$ARCH/boot0
OUT=build/$ARCH/boot1
STAGE=build/$ARCH/.boot1-stage
-for bin in hex2 M0 catm; do
- [ -x "$BOOT0/$bin" ] || {
- echo "[boot1 $ARCH] missing prerequisite: $BOOT0/$bin (run scripts/boot0.sh $ARCH)" >&2
- exit 1
- }
-done
-
-case "$DRIVER" in
- podman)
- IMAGE=boot2-scratch:$ARCH
- if ! podman image exists "$IMAGE"; then
- echo "[boot1 $ARCH] building $IMAGE"
- podman build --platform "$PLATFORM" -t "$IMAGE" \
- -f scripts/Containerfile.scratch scripts/
- fi
- export PLATFORM IMAGE ;;
- seed)
- case "$ARCH" in
- aarch64) KIMG=Image ;;
- amd64) KIMG=kernel.elf ;;
- riscv64) KIMG=kernel.elf ;;
- *) echo "[boot1] DRIVER=seed: amd64|aarch64|riscv64 only (got $ARCH)" >&2; exit 2 ;;
- esac
- KERNEL_IMAGE=$ROOT/build/$ARCH/boot6/$KIMG
- EXTRACT=$ROOT/seed-kernel/scripts/extract-blk.sh
- [ -f "$KERNEL_IMAGE" ] || { echo "[boot1] missing $KERNEL_IMAGE — run ./scripts/boot.sh $ARCH (default DRIVER=podman) first" >&2; exit 1; }
- export KERNEL_IMAGE EXTRACT SEED_ARCH=$ARCH ;;
- *) echo "[boot1] unknown DRIVER=$DRIVER" >&2; exit 2 ;;
-esac
+require_prev "$BOOT0" hex2 M0 catm
. scripts/lib-pipeline.sh
pipeline_init "$STAGE" "$OUT" "$DRIVER"
@@ -83,7 +43,7 @@ pipeline_input M1pp.P1 "M1pp/M1pp.P1"
pipeline_input hex2pp.P1 "hex2pp/hex2pp.P1"
# ─── pipeline ─────────────────────────────────────────────────────────
-echo "[boot1 $ARCH/$DRIVER] M1pp.P1 + hex2pp.P1 -> M1pp + hex2pp"
+echo "[$BOOT_TAG] M1pp.P1 + hex2pp.P1 -> M1pp + hex2pp"
# .P1 -> ELF via M0 + hex2:
# catm P1.M1 + src -> combined.M1
@@ -108,4 +68,4 @@ pipeline_export hex2pp
pipeline_run
-echo "[boot1 $ARCH/$DRIVER] OK -> $OUT/{M1pp, hex2pp}"
+echo "[$BOOT_TAG] OK -> $OUT/{M1pp, hex2pp}"
diff --git a/scripts/boot2.sh b/scripts/boot2.sh
@@ -19,64 +19,21 @@
## build/$ARCH/boot2/{catm, scheme1}
##
## Usage: scripts/boot2.sh <arch>
-## <arch> ∈ {aarch64, amd64, riscv64} for DRIVER=podman (default).
-## DRIVER=seed currently supports aarch64 only.
+## <arch> ∈ {aarch64, amd64, riscv64} for either DRIVER (default podman).
set -eu
-usage() { echo "usage: $0 <aarch64|amd64|riscv64>" >&2; exit 2; }
-[ "$#" -eq 1 ] || usage
-ARCH=$1
+. scripts/lib-arch.sh
+bootlib_init boot2 "${1:-}"
+driver_init scratch
-case "$ARCH" in
- aarch64) PLATFORM=linux/arm64 ;;
- amd64) PLATFORM=linux/amd64 ;;
- riscv64) PLATFORM=linux/riscv64 ;;
- *) usage ;;
-esac
-
-ROOT=$(cd "$(dirname "$0")/.." && pwd)
-cd "$ROOT"
-
-DRIVER=${DRIVER:-podman}
BOOT0=build/$ARCH/boot0
BOOT1=build/$ARCH/boot1
OUT=build/$ARCH/boot2
STAGE=build/$ARCH/.boot2-stage
-[ -x "$BOOT0/catm" ] || {
- echo "[boot2 $ARCH] missing prerequisite: $BOOT0/catm (run scripts/boot0.sh $ARCH)" >&2
- exit 1
-}
-for bin in M1pp hex2pp; do
- [ -x "$BOOT1/$bin" ] || {
- echo "[boot2 $ARCH] missing prerequisite: $BOOT1/$bin (run scripts/boot1.sh $ARCH)" >&2
- exit 1
- }
-done
-
-case "$DRIVER" in
- podman)
- IMAGE=boot2-scratch:$ARCH
- if ! podman image exists "$IMAGE"; then
- echo "[boot2 $ARCH] building $IMAGE"
- podman build --platform "$PLATFORM" -t "$IMAGE" \
- -f scripts/Containerfile.scratch scripts/
- fi
- export PLATFORM IMAGE ;;
- seed)
- case "$ARCH" in
- aarch64) KIMG=Image ;;
- amd64) KIMG=kernel.elf ;;
- riscv64) KIMG=kernel.elf ;;
- *) echo "[boot2] DRIVER=seed: amd64|aarch64|riscv64 only (got $ARCH)" >&2; exit 2 ;;
- esac
- KERNEL_IMAGE=$ROOT/build/$ARCH/boot6/$KIMG
- EXTRACT=$ROOT/seed-kernel/scripts/extract-blk.sh
- [ -f "$KERNEL_IMAGE" ] || { echo "[boot2] missing $KERNEL_IMAGE — run ./scripts/boot.sh $ARCH (default DRIVER=podman) first" >&2; exit 1; }
- export KERNEL_IMAGE EXTRACT SEED_ARCH=$ARCH ;;
- *) echo "[boot2] unknown DRIVER=$DRIVER" >&2; exit 2 ;;
-esac
+require_prev "$BOOT0" catm
+require_prev "$BOOT1" M1pp hex2pp
. scripts/lib-pipeline.sh
pipeline_init "$STAGE" "$OUT" "$DRIVER"
@@ -93,7 +50,7 @@ pipeline_input catm.P1pp "catm/catm.P1pp"
pipeline_input scheme1.P1pp "scheme1/scheme1.P1pp"
# ─── pipeline ─────────────────────────────────────────────────────────
-echo "[boot2 $ARCH/$DRIVER] catm.P1pp -> catm; scheme1.P1pp -> scheme1"
+echo "[$BOOT_TAG] catm.P1pp -> catm; scheme1.P1pp -> scheme1"
# .P1pp -> ELF:
# catm backend + frontend + libp1pp + src -> combined.M1pp
@@ -120,4 +77,4 @@ pipeline_export scheme1
pipeline_run
-echo "[boot2 $ARCH/$DRIVER] OK -> $OUT/{catm, scheme1}"
+echo "[$BOOT_TAG] OK -> $OUT/{catm, scheme1}"
diff --git a/scripts/boot3.sh b/scripts/boot3.sh
@@ -46,27 +46,14 @@
## scripts/boot4.sh
##
## Usage: scripts/boot3.sh <arch>
-## <arch> ∈ {aarch64, amd64, riscv64} for DRIVER=podman (default).
-## DRIVER=seed currently supports aarch64 only (uses seed-kernel).
+## <arch> ∈ {aarch64, amd64, riscv64} for either DRIVER (default podman).
set -eu
-usage() { echo "usage: $0 <aarch64|amd64|riscv64>" >&2; exit 2; }
-[ "$#" -eq 1 ] || usage
-ARCH=$1
+. scripts/lib-arch.sh
+bootlib_init boot3 "${1:-}"
+driver_init empty
-case "$ARCH" in
- aarch64) PLATFORM=linux/arm64 ;;
- amd64) PLATFORM=linux/amd64 ;;
- riscv64) PLATFORM=linux/riscv64 ;;
- *) usage ;;
-esac
-
-ROOT=$(cd "$(dirname "$0")/.." && pwd)
-cd "$ROOT"
-
-DRIVER=${DRIVER:-podman}
-IMAGE=boot2-empty:$ARCH
BOOT1=build/$ARCH/boot1
BOOT2=build/$ARCH/boot2
OUT=build/$ARCH/boot3
@@ -77,42 +64,20 @@ TCC_DIR=$TCC_VENDOR/tcc-0.9.26-1147-gee75a10c
TCC_FLAT=$TCC_VENDOR/tcc.flat.c
LIBC_FLAT=build/$ARCH/vendor/mes-libc/libc.flat.c
-# ── ensure container image exists (podman driver only) ────────────────
-if [ "$DRIVER" = podman ] && ! podman image exists "$IMAGE"; then
- echo "[boot3 $ARCH] building $IMAGE"
- podman build --no-cache --platform "$PLATFORM" -t "$IMAGE" \
- -f scripts/Containerfile.empty scripts/
-fi
-if [ "$DRIVER" = seed ]; then
- case "$ARCH" in
- aarch64) KIMG=Image ;;
- amd64) KIMG=kernel.elf ;;
- riscv64) KIMG=kernel.elf ;;
- *) echo "[boot3] DRIVER=seed: amd64|aarch64|riscv64 only (got $ARCH)" >&2; exit 2 ;;
- esac
- KERNEL_IMAGE=$ROOT/build/$ARCH/boot6/$KIMG
- EXTRACT=$ROOT/seed-kernel/scripts/extract-blk.sh
- [ -f "$KERNEL_IMAGE" ] || { echo "[boot3] missing $KERNEL_IMAGE — run ./scripts/boot.sh $ARCH (default DRIVER=podman) first" >&2; exit 1; }
- export KERNEL_IMAGE EXTRACT SEED_ARCH=$ARCH
-fi
-export IMAGE PLATFORM DRIVER
-
# ── prerequisite: prior-stage binaries ────────────────────────────────
-[ -x "$BOOT1/M1pp" ] || { echo "[boot3 $ARCH] missing $BOOT1/M1pp (run scripts/boot1.sh $ARCH)" >&2; exit 1; }
-[ -x "$BOOT1/hex2pp" ] || { echo "[boot3 $ARCH] missing $BOOT1/hex2pp (run scripts/boot1.sh $ARCH)" >&2; exit 1; }
-[ -x "$BOOT2/catm" ] || { echo "[boot3 $ARCH] missing $BOOT2/catm (run scripts/boot2.sh $ARCH)" >&2; exit 1; }
-[ -x "$BOOT2/scheme1" ] || { echo "[boot3 $ARCH] missing $BOOT2/scheme1 (run scripts/boot2.sh $ARCH)" >&2; exit 1; }
+require_prev "$BOOT1" M1pp hex2pp
+require_prev "$BOOT2" catm scheme1
# ── prerequisite: host-flattened sources + unpacked tcc tree ──────────
# tcc.flat.c + the unpacked $TCC_DIR/{include,lib} tree are produced
# together by stage1-flatten.sh; libc.flat.c by libc-flatten.sh. Both
# run on the host (cc -E), no container — auto-invoke if missing.
if [ ! -e "$TCC_FLAT" ] || [ ! -d "$TCC_DIR/include" ] || [ ! -e "$TCC_VENDOR/stdarg-bridge.h" ]; then
- echo "[boot3 $ARCH] flatten tcc.flat.c (host)"
+ echo "[$BOOT_TAG] flatten tcc.flat.c (host)"
scripts/stage1-flatten.sh --arch "$ARCH"
fi
if [ ! -e "$LIBC_FLAT" ]; then
- echo "[boot3 $ARCH] flatten libc.flat.c (host)"
+ echo "[$BOOT_TAG] flatten libc.flat.c (host)"
scripts/libc-flatten.sh --arch "$ARCH"
fi
@@ -153,5 +118,5 @@ runscm_input libc.flat.c "$LIBC_FLAT"
runscm_export tcc0
runscm_run "${BOOT3_TIMEOUT:-1800}"
-echo "[boot3 $ARCH/$DRIVER] sizes: tcc0=$(wc -c <"$OUT/tcc0")"
-echo "[boot3 $ARCH/$DRIVER] OK -> $OUT/tcc0"
+echo "[$BOOT_TAG] sizes: tcc0=$(wc -c <"$OUT/tcc0")"
+echo "[$BOOT_TAG] OK -> $OUT/tcc0"
diff --git a/scripts/boot4.sh b/scripts/boot4.sh
@@ -76,33 +76,20 @@
## script) — that equality is the fixed-point check.
##
## Usage: scripts/boot4.sh <arch>
-## <arch> ∈ {aarch64, amd64, riscv64} for DRIVER=podman (default).
-## DRIVER=seed currently supports aarch64 only (uses seed-kernel).
+## <arch> ∈ {aarch64, amd64, riscv64} for either DRIVER (default podman).
set -eu
-usage() { echo "usage: $0 <aarch64|amd64|riscv64>" >&2; exit 2; }
-[ "$#" -eq 1 ] || usage
-ARCH=$1
+. scripts/lib-arch.sh
+bootlib_init boot4 "${1:-}"
+driver_init empty
case "$ARCH" in
- aarch64) PLATFORM=linux/arm64;
- LIBTCC1_C_SRCS="lib-arm64.c";
- LIBTCC1_ASM_SRCS="" ;;
- amd64) PLATFORM=linux/amd64;
- LIBTCC1_C_SRCS="libtcc1.c va_list.c";
- LIBTCC1_ASM_SRCS="alloca86_64.S alloca86_64-bt.S" ;;
- riscv64) PLATFORM=linux/riscv64;
- LIBTCC1_C_SRCS="lib-arm64.c";
- LIBTCC1_ASM_SRCS="" ;;
- *) usage ;;
+ aarch64) LIBTCC1_C_SRCS="lib-arm64.c"; LIBTCC1_ASM_SRCS="" ;;
+ amd64) LIBTCC1_C_SRCS="libtcc1.c va_list.c"; LIBTCC1_ASM_SRCS="alloca86_64.S alloca86_64-bt.S" ;;
+ riscv64) LIBTCC1_C_SRCS="lib-arm64.c"; LIBTCC1_ASM_SRCS="" ;;
esac
-ROOT=$(cd "$(dirname "$0")/.." && pwd)
-cd "$ROOT"
-
-DRIVER=${DRIVER:-podman}
-IMAGE=boot2-empty:$ARCH
BOOT2=build/$ARCH/boot2
BOOT3=build/$ARCH/boot3
OUT=build/$ARCH/boot4
@@ -113,45 +100,24 @@ TCC_DIR=$TCC_VENDOR/tcc-0.9.26-1147-gee75a10c
TCC_FLAT=$TCC_VENDOR/tcc.flat.c
LIBC_FLAT=build/$ARCH/vendor/mes-libc/libc.flat.c
-# ── ensure container image exists (podman driver only) ────────────────
-if [ "$DRIVER" = podman ] && ! podman image exists "$IMAGE"; then
- echo "[boot4 $ARCH] building $IMAGE"
- podman build --no-cache --platform "$PLATFORM" -t "$IMAGE" \
- -f scripts/Containerfile.empty scripts/
-fi
-if [ "$DRIVER" = seed ]; then
- case "$ARCH" in
- aarch64) KIMG=Image ;;
- amd64) KIMG=kernel.elf ;;
- riscv64) KIMG=kernel.elf ;;
- *) echo "[boot4] DRIVER=seed: amd64|aarch64|riscv64 only (got $ARCH)" >&2; exit 2 ;;
- esac
- KERNEL_IMAGE=$ROOT/build/$ARCH/boot6/$KIMG
- EXTRACT=$ROOT/seed-kernel/scripts/extract-blk.sh
- [ -f "$KERNEL_IMAGE" ] || { echo "[boot4] missing $KERNEL_IMAGE — run ./scripts/boot.sh $ARCH (default DRIVER=podman) first" >&2; exit 1; }
- export KERNEL_IMAGE EXTRACT SEED_ARCH=$ARCH
-fi
-export IMAGE PLATFORM DRIVER
-
# ── prerequisite: prior-stage binaries ────────────────────────────────
-[ -x "$BOOT3/tcc0" ] || { echo "[boot4 $ARCH] missing $BOOT3/tcc0 (run scripts/boot3.sh $ARCH)" >&2; exit 1; }
-[ -x "$BOOT2/scheme1" ] || { echo "[boot4 $ARCH] missing $BOOT2/scheme1 (run scripts/boot2.sh $ARCH)" >&2; exit 1; }
-[ -x "$BOOT2/catm" ] || { echo "[boot4 $ARCH] missing $BOOT2/catm (run scripts/boot2.sh $ARCH)" >&2; exit 1; }
+require_prev "$BOOT3" tcc0
+require_prev "$BOOT2" catm scheme1
# ── prerequisite: host-flattened sources + unpacked tcc tree ──────────
# Normally these were produced by boot3 (auto-invoked by stage1-flatten
# / libc-flatten there). Re-check here so boot4 runs standalone if a
# user has tcc0 but blew away build/$ARCH/vendor/tcc/.
if [ ! -e "$TCC_FLAT" ] || [ ! -d "$TCC_DIR/include" ] || [ ! -e "$TCC_DIR/lib/lib-arm64.c" ] || [ ! -e "$TCC_VENDOR/stdarg-bridge.h" ]; then
- echo "[boot4 $ARCH] flatten tcc.flat.c (host)"
+ echo "[$BOOT_TAG] flatten tcc.flat.c (host)"
scripts/stage1-flatten.sh --arch "$ARCH"
fi
if [ ! -e "$LIBC_FLAT" ]; then
- echo "[boot4 $ARCH] flatten libc.flat.c (host)"
+ echo "[$BOOT_TAG] flatten libc.flat.c (host)"
scripts/libc-flatten.sh --arch "$ARCH"
fi
for f in $LIBTCC1_C_SRCS $LIBTCC1_ASM_SRCS; do
- [ -e "$TCC_DIR/lib/$f" ] || { echo "[boot4 $ARCH] missing $TCC_DIR/lib/$f" >&2; exit 1; }
+ [ -e "$TCC_DIR/lib/$f" ] || { echo "[$BOOT_TAG] missing $TCC_DIR/lib/$f" >&2; exit 1; }
done
# ── stage inputs and run scheme1 + boot4 run.scm under $DRIVER ────────
@@ -160,7 +126,7 @@ runscm_init "$STAGE" "$OUT"
RUNSCM=$STAGE/run.scm
scripts/boot4-gen-runscm.sh "$ARCH" "$RUNSCM"
-echo "[boot4 $ARCH] generated run.scm: $(wc -l <"$RUNSCM") lines"
+echo "[$BOOT_TAG] generated run.scm: $(wc -l <"$RUNSCM") lines"
runscm_scheme1 "$BOOT2/scheme1"
runscm_prelude scheme1/prelude.scm
@@ -201,9 +167,9 @@ if ! cmp -s "$OUT/tcc2" "$OUT/tcc3"; then
s2=$(wc -c <"$OUT/tcc2")
s3=$(wc -c <"$OUT/tcc3")
if [ "${TCC_BOOTSTRAP_RELAX_FIXEDPOINT:-0}" = 1 ]; then
- echo "[boot4 $ARCH] WARN: tcc2 ($s2) != tcc3 ($s3); TCC_BOOTSTRAP_RELAX_FIXEDPOINT=1, accepting tcc3" >&2
+ echo "[$BOOT_TAG] WARN: tcc2 ($s2) != tcc3 ($s3); TCC_BOOTSTRAP_RELAX_FIXEDPOINT=1, accepting tcc3" >&2
else
- echo "[boot4 $ARCH] FIXED-POINT FAIL: tcc2 ($s2) != tcc3 ($s3)" >&2
+ echo "[$BOOT_TAG] FIXED-POINT FAIL: tcc2 ($s2) != tcc3 ($s3)" >&2
exit 1
fi
fi
@@ -215,5 +181,5 @@ mv "$OUT/s3-libtcc1.a" "$OUT/libtcc1.a"
rm -f "$OUT/tcc1" "$OUT/tcc2"
chmod 0700 "$OUT/tcc3" "$OUT/hello"
-echo "[boot4 $ARCH/$DRIVER] sizes: libtcc1.a=$(wc -c <"$OUT/libtcc1.a") libc.a=$(wc -c <"$OUT/libc.a") hello=$(wc -c <"$OUT/hello")"
-echo "[boot4 $ARCH/$DRIVER] OK -> $OUT/{tcc3, crt1.o, libc.a, libtcc1.a, hello} (fixed point: tcc2 == tcc3)"
+echo "[$BOOT_TAG] sizes: libtcc1.a=$(wc -c <"$OUT/libtcc1.a") libc.a=$(wc -c <"$OUT/libc.a") hello=$(wc -c <"$OUT/hello")"
+echo "[$BOOT_TAG] OK -> $OUT/{tcc3, crt1.o, libc.a, libtcc1.a, hello} (fixed point: tcc2 == tcc3)"
diff --git a/scripts/boot5.sh b/scripts/boot5.sh
@@ -46,37 +46,14 @@
## build/$ARCH/boot5/hello — static, runs in the container
##
## Usage: scripts/boot5.sh <arch>
-## <arch> ∈ {amd64, aarch64, riscv64} for DRIVER=podman (default).
-## All three architectures are verified end-to-end on podman.
-## DRIVER=seed: aarch64 only. Drives ~1300 (run "tcc" …) calls through
-## the seed kernel's atomic spawn syscall (no per-fork memcpy). Wall
-## time is dominated by tcc work, not spawn overhead.
+## <arch> ∈ {amd64, aarch64, riscv64} for either DRIVER (default podman).
set -eu
-usage() { echo "usage: $0 <amd64|aarch64|riscv64>" >&2; exit 2; }
-[ "$#" -eq 1 ] || usage
-ARCH=$1
+. scripts/lib-arch.sh
+bootlib_init boot5 "${1:-}"
+driver_init empty
-case "$ARCH" in
- amd64) PLATFORM=linux/amd64; MUSL_ARCH=x86_64 ;;
- aarch64) PLATFORM=linux/arm64; MUSL_ARCH=aarch64 ;;
- riscv64) PLATFORM=linux/riscv64; MUSL_ARCH=riscv64 ;;
- *) usage ;;
-esac
-
-ROOT=$(cd "$(dirname "$0")/.." && pwd)
-cd "$ROOT"
-
-DRIVER=${DRIVER:-podman}
-if [ "$DRIVER" = seed ]; then
- case "$ARCH" in
- amd64|aarch64|riscv64) ;;
- *) echo "[boot5] DRIVER=seed: amd64|aarch64|riscv64 only (got $ARCH)" >&2; exit 2 ;;
- esac
-fi
-
-IMAGE=boot2-empty:$ARCH
BOOT2=build/$ARCH/boot2
BOOT4=build/$ARCH/boot4
OUT=build/$ARCH/boot5
@@ -89,34 +66,15 @@ MUSL_SKIP=vendor/upstream/musl-1.2.5-skip-$ARCH.txt
BRIDGE_FILE=build/$ARCH/vendor/tcc/stdarg-bridge.h
# ── prerequisites ─────────────────────────────────────────────────────
-[ -x "$BOOT4/tcc3" ] || { echo "[boot5 $ARCH] missing $BOOT4/tcc3 (run scripts/boot4.sh $ARCH)" >&2; exit 1; }
-[ -e "$BOOT4/libtcc1.a" ] || { echo "[boot5 $ARCH] missing $BOOT4/libtcc1.a (run scripts/boot4.sh $ARCH)" >&2; exit 1; }
-[ -x "$BOOT2/scheme1" ] || { echo "[boot5 $ARCH] missing $BOOT2/scheme1 (run scripts/boot2.sh $ARCH)" >&2; exit 1; }
-[ -x "$BOOT2/catm" ] || { echo "[boot5 $ARCH] missing $BOOT2/catm (run scripts/boot2.sh $ARCH)" >&2; exit 1; }
-[ -e "$MUSL_TARBALL" ] || { echo "[boot5 $ARCH] missing $MUSL_TARBALL" >&2; exit 1; }
-[ -d "$MUSL_OVERRIDES" ] || { echo "[boot5 $ARCH] missing $MUSL_OVERRIDES" >&2; exit 1; }
-[ -e "$MUSL_DELETES" ] || { echo "[boot5 $ARCH] missing $MUSL_DELETES" >&2; exit 1; }
-[ -d "$MUSL_GENERATED" ] || { echo "[boot5 $ARCH] missing $MUSL_GENERATED (run scripts/musl-vendor.sh)" >&2; exit 1; }
-[ -e "$MUSL_SKIP" ] || { echo "[boot5 $ARCH] missing $MUSL_SKIP (run scripts/boot5-calibrate.sh $ARCH)" >&2; exit 1; }
-[ -e "$BRIDGE_FILE" ] || { echo "[boot5 $ARCH] missing $BRIDGE_FILE (run scripts/stage1-flatten.sh)" >&2; exit 1; }
-
-if [ "$DRIVER" = podman ] && ! podman image exists "$IMAGE"; then
- echo "[boot5 $ARCH] building $IMAGE"
- podman build --no-cache --platform "$PLATFORM" -t "$IMAGE" \
- -f scripts/Containerfile.empty scripts/
-fi
-if [ "$DRIVER" = seed ]; then
- case "$ARCH" in
- aarch64) KIMG=Image ;;
- amd64) KIMG=kernel.elf ;;
- riscv64) KIMG=kernel.elf ;;
- esac
- KERNEL_IMAGE=$ROOT/build/$ARCH/boot6/$KIMG
- EXTRACT=$ROOT/seed-kernel/scripts/extract-blk.sh
- [ -f "$KERNEL_IMAGE" ] || { echo "[boot5] missing $KERNEL_IMAGE — run ./scripts/boot.sh $ARCH (default DRIVER=podman) first" >&2; exit 1; }
- export KERNEL_IMAGE EXTRACT SEED_ARCH=$ARCH
-fi
-export IMAGE PLATFORM DRIVER
+require_prev "$BOOT4" tcc3
+require_prev "$BOOT2" catm scheme1
+[ -e "$BOOT4/libtcc1.a" ] || { echo "[$BOOT_TAG] missing $BOOT4/libtcc1.a (run scripts/boot4.sh $ARCH)" >&2; exit 1; }
+[ -e "$MUSL_TARBALL" ] || { echo "[$BOOT_TAG] missing $MUSL_TARBALL" >&2; exit 1; }
+[ -d "$MUSL_OVERRIDES" ] || { echo "[$BOOT_TAG] missing $MUSL_OVERRIDES" >&2; exit 1; }
+[ -e "$MUSL_DELETES" ] || { echo "[$BOOT_TAG] missing $MUSL_DELETES" >&2; exit 1; }
+[ -d "$MUSL_GENERATED" ] || { echo "[$BOOT_TAG] missing $MUSL_GENERATED (run scripts/musl-vendor.sh)" >&2; exit 1; }
+[ -e "$MUSL_SKIP" ] || { echo "[$BOOT_TAG] missing $MUSL_SKIP (run scripts/boot5-calibrate.sh $ARCH)" >&2; exit 1; }
+[ -e "$BRIDGE_FILE" ] || { echo "[$BOOT_TAG] missing $BRIDGE_FILE (run scripts/stage1-flatten.sh)" >&2; exit 1; }
# ── prepare staging dirs and musl tree on host ────────────────────────
# $STAGE/in/ — read-only inputs (becomes /work/in or in/ in tmpfs)
@@ -208,7 +166,7 @@ awk -v SKIPF="$MUSL_SKIP" '
n_src=$(wc -l < "$STAGE/_host/build-srcs.txt")
n_skip=$(wc -l < "$MUSL_SKIP")
-echo "[boot5 $ARCH] keep=$n_src skip=$n_skip (calibrated)"
+echo "[$BOOT_TAG] keep=$n_src skip=$n_skip (calibrated)"
# Record CRT mode (asm vs c) so the gen-runscm step picks the right
# crti/crtn source set without re-checking $MUSL_DIR.
@@ -242,7 +200,7 @@ echo '#define VERSION "1.2.5-tcc-boot5"' > "$MUSL_DIR/obj/src/internal/version.h
# ── generate run.scm and stage chain binaries ─────────────────────────
RUNSCM=$STAGE/run.scm
scripts/boot5-gen-runscm.sh "$MUSL_ARCH" "$STAGE/_host" "$RUNSCM"
-echo "[boot5 $ARCH] generated run.scm: $(wc -l <"$RUNSCM") lines, $(wc -c <"$RUNSCM") bytes"
+echo "[$BOOT_TAG] generated run.scm: $(wc -l <"$RUNSCM") lines, $(wc -c <"$RUNSCM") bytes"
runscm_scheme1 "$BOOT2/scheme1"
runscm_prelude scheme1/prelude.scm
@@ -269,5 +227,5 @@ runscm_export hello
# the seed driver. Podman ignores QEMU_MEM and uses host memory directly.
QEMU_MEM=${QEMU_MEM:-3072M} runscm_run "${BOOT5_TIMEOUT:-7200}"
-echo "[boot5 $ARCH/$DRIVER] sizes: libc.a=$(wc -c <"$OUT/libc.a") hello=$(wc -c <"$OUT/hello")"
-echo "[boot5 $ARCH/$DRIVER] OK -> $OUT/{libc.a, crt1.o, crti.o, crtn.o, hello}"
+echo "[$BOOT_TAG] sizes: libc.a=$(wc -c <"$OUT/libc.a") hello=$(wc -c <"$OUT/hello")"
+echo "[$BOOT_TAG] OK -> $OUT/{libc.a, crt1.o, crti.o, crtn.o, hello}"
diff --git a/scripts/boot6.sh b/scripts/boot6.sh
@@ -41,69 +41,40 @@
set -eu
-usage() { echo "usage: $0 <amd64|aarch64|riscv64>" >&2; exit 2; }
-[ "$#" -eq 1 ] || usage
-ARCH=$1
+. scripts/lib-arch.sh
+bootlib_init boot6 "${1:-}"
+driver_init empty
-case "$ARCH" in
- amd64) PLATFORM=linux/amd64; ARCHDIR=amd64; OUT_FILE=kernel.elf ;;
- aarch64) PLATFORM=linux/arm64; ARCHDIR=aarch64; OUT_FILE=Image ;;
- riscv64) PLATFORM=linux/riscv64; ARCHDIR=riscv64; OUT_FILE=kernel.elf ;;
- *) usage ;;
-esac
-
-ROOT=$(cd "$(dirname "$0")/.." && pwd)
-cd "$ROOT"
-
-DRIVER=${DRIVER:-podman}
-case "$DRIVER:$ARCH" in
- seed:amd64|seed:aarch64|seed:riscv64) ;;
- seed:*) echo "[boot6] DRIVER=seed: amd64|aarch64|riscv64 only (got $ARCH)" >&2; exit 2 ;;
-esac
-IMAGE=boot2-empty:$ARCH
+OUT_FILE=$KERNEL_NAME
BOOT2=build/$ARCH/boot2
BOOT4=build/$ARCH/boot4
OUT=build/$ARCH/boot6
STAGE=build/$ARCH/.boot6-stage
# ── prerequisites ─────────────────────────────────────────────────────
-[ -x "$BOOT4/tcc3" ] || { echo "[boot6 $ARCH] missing $BOOT4/tcc3 (run scripts/boot4.sh $ARCH)" >&2; exit 1; }
-[ -x "$BOOT2/scheme1" ] || { echo "[boot6 $ARCH] missing $BOOT2/scheme1 (run scripts/boot2.sh $ARCH)" >&2; exit 1; }
-for f in seed-kernel/arch/$ARCHDIR/kernel.S seed-kernel/arch/$ARCHDIR/mmu.c seed-kernel/arch/$ARCHDIR/arch.h seed-kernel/kernel.c tcc-cc/mem.c; do
- [ -f "$f" ] || { echo "[boot6 $ARCH] missing $f" >&2; exit 1; }
+require_prev "$BOOT4" tcc3
+require_prev "$BOOT2" scheme1
+for f in seed-kernel/arch/$ARCH/kernel.S seed-kernel/arch/$ARCH/mmu.c seed-kernel/arch/$ARCH/arch.h seed-kernel/kernel.c tcc-cc/mem.c; do
+ [ -f "$f" ] || { echo "[$BOOT_TAG] missing $f" >&2; exit 1; }
done
-# ── ensure container image exists (podman driver only) ────────────────
-if [ "$DRIVER" = podman ] && ! podman image exists "$IMAGE"; then
- echo "[boot6 $ARCH] building $IMAGE"
- podman build --no-cache --platform "$PLATFORM" -t "$IMAGE" \
- -f scripts/Containerfile.empty scripts/
-fi
-if [ "$DRIVER" = seed ]; then
- KERNEL_IMAGE=$ROOT/build/$ARCH/boot6/$OUT_FILE
- EXTRACT=$ROOT/seed-kernel/scripts/extract-blk.sh
- [ -f "$KERNEL_IMAGE" ] || { echo "[boot6] missing $KERNEL_IMAGE — run ./scripts/boot.sh $ARCH (default DRIVER=podman) first" >&2; exit 1; }
- export KERNEL_IMAGE EXTRACT SEED_ARCH=$ARCH
-fi
-export IMAGE PLATFORM DRIVER
-
# ── stage inputs and run scheme1 + run.scm under $DRIVER ──────────────
. scripts/lib-runscm.sh
runscm_init "$STAGE" "$OUT"
RUNSCM=$STAGE/run.scm
scripts/boot6-gen-runscm.sh "$ARCH" "$RUNSCM"
-echo "[boot6 $ARCH] generated run.scm: $(wc -l <"$RUNSCM") lines"
+echo "[$BOOT_TAG] generated run.scm: $(wc -l <"$RUNSCM") lines"
runscm_scheme1 "$BOOT2/scheme1"
runscm_prelude scheme1/prelude.scm
runscm_runscm "$RUNSCM"
runscm_input tcc3 "$BOOT4/tcc3"
-runscm_input kernel.S seed-kernel/arch/$ARCHDIR/kernel.S
+runscm_input kernel.S seed-kernel/arch/$ARCH/kernel.S
runscm_input kernel.c seed-kernel/kernel.c
-runscm_input arch.h seed-kernel/arch/$ARCHDIR/arch.h
-runscm_input mmu.c seed-kernel/arch/$ARCHDIR/mmu.c
+runscm_input arch.h seed-kernel/arch/$ARCH/arch.h
+runscm_input mmu.c seed-kernel/arch/$ARCH/mmu.c
runscm_input mem.c tcc-cc/mem.c
# amd64 needs a post-link fixup — tcc3 doesn't emit PT_NOTE phdrs, so
@@ -120,4 +91,4 @@ fi
runscm_export "$OUT_FILE"
runscm_run 1200
-echo "[boot6 $ARCH/$DRIVER] OK -> $OUT/$OUT_FILE ($(wc -c <"$OUT/$OUT_FILE") bytes)"
+echo "[$BOOT_TAG] OK -> $OUT/$OUT_FILE ($(wc -c <"$OUT/$OUT_FILE") bytes)"
diff --git a/scripts/lib-arch.sh b/scripts/lib-arch.sh
@@ -0,0 +1,99 @@
+# lib-arch.sh — single source for arch + driver setup shared by
+# scripts/boot.sh, scripts/boot{0..6}.sh, lib-pipeline.sh, lib-runscm.sh.
+#
+# Public entry points (call in this order from a bootN.sh):
+#
+# bootlib_init <stage> <arch> # validate <arch>, cd to repo root,
+# # set ARCH/PLATFORM/KERNEL_NAME/
+# # MUSL_ARCH/DRIVER/BOOT_TAG.
+# driver_init [<image-kind>] # podman: build IMAGE if missing
+# # (image-kind ∈ scratch|empty;
+# # default scratch).
+# # seed: verify boot6 kernel exists.
+# require_prev <dir> <name>... # die helpfully if any <dir>/<name>
+# # is missing or non-executable.
+#
+# After bootlib_init, the following shell vars are set/exported:
+# ARCH input architecture token (aarch64|amd64|riscv64)
+# ROOT repo root (cwd is set to ROOT)
+# DRIVER podman|seed (defaults to podman)
+# PLATFORM linux/<arm64|amd64|riscv64> for podman --platform
+# KERNEL_NAME Image (aarch64) | kernel.elf (amd64,riscv64)
+# MUSL_ARCH aarch64 | x86_64 | riscv64
+# BOOT_TAG "<stage>/<driver>/<arch>" for log prefixes
+#
+# After driver_init:
+# podman: IMAGE
+# seed: KERNEL_IMAGE, EXTRACT, SEED_ARCH
+
+bootlib_init() {
+ _stage=$1; _arch=${2:-}
+ [ -n "$_stage" ] || { echo "lib-arch: bootlib_init: stage required" >&2; exit 2; }
+ case "$_arch" in
+ aarch64|amd64|riscv64) ;;
+ *) echo "usage: $0 <aarch64|amd64|riscv64>" >&2; exit 2 ;;
+ esac
+ ARCH=$_arch
+ ROOT=$(cd "$(dirname "$0")/.." && pwd)
+ cd "$ROOT"
+ DRIVER=${DRIVER:-podman}
+ case "$DRIVER" in
+ podman|seed) ;;
+ *) echo "[$_stage/$DRIVER/$ARCH] unknown DRIVER=$DRIVER (expected podman|seed)" >&2; exit 2 ;;
+ esac
+ BOOT_TAG="$_stage/$DRIVER/$ARCH"
+ case "$ARCH" in
+ aarch64) PLATFORM=linux/arm64; KERNEL_NAME=Image; MUSL_ARCH=aarch64 ;;
+ amd64) PLATFORM=linux/amd64; KERNEL_NAME=kernel.elf; MUSL_ARCH=x86_64 ;;
+ riscv64) PLATFORM=linux/riscv64; KERNEL_NAME=kernel.elf; MUSL_ARCH=riscv64 ;;
+ esac
+ export ARCH ROOT DRIVER PLATFORM KERNEL_NAME MUSL_ARCH BOOT_TAG
+}
+
+driver_init() {
+ _image_kind=${1:-scratch}
+ case "$_image_kind" in
+ scratch|empty) ;;
+ *) echo "[$BOOT_TAG] driver_init: image-kind must be scratch|empty (got $_image_kind)" >&2; exit 2 ;;
+ esac
+ case "$DRIVER" in
+ podman)
+ IMAGE=boot2-$_image_kind:$ARCH
+ if ! podman image exists "$IMAGE"; then
+ echo "[$BOOT_TAG] building $IMAGE"
+ # Containerfile.empty drops /etc resolver state etc.; no-cache
+ # avoids a stale layer surviving an upstream tag bump.
+ _no_cache=
+ [ "$_image_kind" = empty ] && _no_cache=--no-cache
+ podman build $_no_cache --platform "$PLATFORM" -t "$IMAGE" \
+ -f scripts/Containerfile.$_image_kind scripts/
+ fi
+ export IMAGE
+ ;;
+ seed)
+ KERNEL_IMAGE=$ROOT/build/$ARCH/boot6/$KERNEL_NAME
+ EXTRACT=$ROOT/seed-kernel/scripts/extract-blk.sh
+ [ -f "$KERNEL_IMAGE" ] || {
+ echo "[$BOOT_TAG] missing $KERNEL_IMAGE — run ./scripts/boot.sh $ARCH (default DRIVER=podman) first" >&2
+ exit 1
+ }
+ export KERNEL_IMAGE EXTRACT
+ export SEED_ARCH=$ARCH
+ ;;
+ esac
+}
+
+require_prev() {
+ _dir=$1; shift
+ for _n in "$@"; do
+ [ -x "$_dir/$_n" ] || {
+ _stage_name=$(basename "$_dir")
+ case "$_stage_name" in
+ boot*) _hint="run scripts/$_stage_name.sh $ARCH" ;;
+ *) _hint="rebuild $_dir" ;;
+ esac
+ echo "[$BOOT_TAG] missing prerequisite: $_dir/$_n ($_hint)" >&2
+ exit 1
+ }
+ done
+}