diff options
Diffstat (limited to 'sbo-batch-test')
| -rwxr-xr-x | sbo-batch-test | 668 |
1 files changed, 668 insertions, 0 deletions
diff --git a/sbo-batch-test b/sbo-batch-test new file mode 100755 index 0000000..d6ca1ef --- /dev/null +++ b/sbo-batch-test @@ -0,0 +1,668 @@ +#!/bin/bash +# +# sbo-batch-test - batch-test SlackBuilds against a clean Slackware 15.0 +# overlay chroot. Non-interactive, dependency-resolving, per-target disposable +# overlay, persistent logs, color summary. +# +# Overlay/chroot mount + teardown machinery follows the patterns in +# overlay-chroot.sh by Jeremy Hansen (bassmadrigal). The teardown ordering +# (pts, dev/proc/sys, resolv.conf, dbus machine-id, overlay last) is preserved +# deliberately, it is the correct unwind order. +# +# Runs INSIDE a Slackware64-current VM. Verifies SlackBuilds BUILD and install +# cleanly against 15.0 userland. Does NOT test kernel modules (shares host +# kernel). Resolution is LOCAL-tree-only, never network. Does not touch +# slackrepo. +# +# No em dashes in prose by author convention. + +# ============================================================================= +# CONFIG (edit these for your VM) +# ============================================================================= + +# LOCAL (non-NFS) read-only 15.0 base install tree. This is the overlay +# lowerdir. MUST be a local filesystem (ext4/xfs). NEVER point this at the NFS +# mirror: overlayfs over NFS lowerdir is fragile and fails intermittently. +SLACKWARE_BASE="/sbo-base/15.0" + +# NFS-mounted 15.0 mirror. Package SOURCE only, used to populate/patch +# SLACKWARE_BASE. Read-only is fine. Never used as the overlay lowerdir. +LOCAL_MIRROR_15="/mnt/nfs/slackware64-15.0" + +# One or more LOCAL SBo tree roots, resolved in order (first match wins). +# Standard SBo layout: <root>/<category>/<prog>/{prog.SlackBuild,prog.info,...}. +# Read in place, never copied or synced. +SBO_TREE_ROOTS=( + "/home/danix/SBo-danix" + "/home/danix/slackbuilds-15.0" +) + +# Where overlays are created. LOCAL. One disposable overlay per target lives here. +CHROOT_LOCATION="/tmp" + +# Where persistent logs are written (outside the overlay, survives teardown). +LOG_ROOT="/var/log/sbo-batch-test" + +# Slackware version, used for the mirror ChangeLog / patches path. +VERSION="15.0" + +# ============================================================================= +# END CONFIG +# ============================================================================= + +set -uo pipefail +# Note: NOT using -e globally. One package build failing is an expected, +# handled outcome, not a script crash. Per-package execution is isolated. + +# ---- flags / globals -------------------------------------------------------- +USE_COLOR=1 # --no-color or non-TTY disables +DRY_RUN=0 # resolve + print build order, do not build +WITH_X=0 # --with-x: optional X passthrough (default headless) +JOBS=1 # -j: reserved, see TODO +TARGET_ARG="" + +RUN_DIR="" # timestamped log dir for this run +declare -a ACTIVE_MOUNTS=() # overlay chroot roots currently mounted (for trap unwind) +CURRENT_OVERLAY="" # the $TMPDIR of the overlay being built in now + +# Status tracking. Keyed by "category/prog". Parallel assoc arrays. +declare -A ST_STATUS=() # SUCCESS|BUILD-FAILED|DOWNLOAD-FAILED|MD5-MISMATCH|INSTALL-FAILED|BLOCKED-BY-DEP|UNMET-DEP +declare -A ST_REASON=() +declare -A ST_TIME=() # elapsed seconds +declare -A ST_README=() # 1 if package carries %README% + +# ============================================================================= +# usage +# ============================================================================= +usage() { + cat <<'EOF' +sbo-batch-test - batch-test SlackBuilds against a clean Slackware 15.0 overlay chroot + +USAGE: + sbo-batch-test [OPTIONS] <program-name> + sbo-batch-test [OPTIONS] <category-folder> + +MODES: + <program-name> Resolve its full SBo dependency tree, build+install every + dep in topological order, then build+install the target. + <category-folder> Treat every SlackBuild dir inside as an independent target. + Each target gets its OWN fresh overlay against pristine 15.0. + +OPTIONS: + -h, --help This text. + --no-color Disable ANSI color (auto-disabled when stdout is not a TTY). + --dry-run Resolve and print the build order, do not build. + --with-x Enable X passthrough via xhost +local:hosts (security caveat: + allows local non-network connections to your X server). + Default is headless (no X). + -j, --jobs N Reserved. Currently a no-op stub (builds are serial). + +EXTENSION POINTS (not implemented, see TODOs in source): + - "all" mode (build every package across all SBo roots). + - queue/list-file mode (build a named list of targets). + - -j parallelism. + +EOF +} + +# ============================================================================= +# color helpers +# ============================================================================= +init_color() { + if [[ $USE_COLOR -eq 1 && -t 1 ]]; then + C_RED=$'\e[31m'; C_GRN=$'\e[32m'; C_YEL=$'\e[33m'; C_RST=$'\e[0m' + else + C_RED=""; C_GRN=""; C_YEL=""; C_RST="" + fi +} + +# ============================================================================= +# arg parsing +# ============================================================================= +parse_args() { + while [[ $# -gt 0 ]]; do + case "$1" in + -h|--help) usage; exit 0 ;; + --no-color) USE_COLOR=0; shift ;; + --dry-run) DRY_RUN=1; shift ;; + --with-x) WITH_X=1; shift ;; + -j|--jobs) JOBS="${2:-1}"; shift 2 ;; # TODO: implement parallelism + -*) echo "Unknown option: $1" >&2; usage >&2; exit 2 ;; + *) + if [[ -n "$TARGET_ARG" ]]; then + echo "Only one target argument accepted (got '$TARGET_ARG' and '$1')." >&2 + exit 2 + fi + TARGET_ARG="$1"; shift ;; + esac + done + if [[ -z "$TARGET_ARG" ]]; then + echo "No target given." >&2; usage >&2; exit 2 + fi +} + +# ============================================================================= +# startup validation (fail fast, copy-pasteable hints) +# ============================================================================= +validate_env() { + if [[ $EUID -ne 0 ]]; then + echo "This tool must run as root (overlay + chroot)." >&2 + exit 1 + fi + + # SLACKWARE_BASE: local, looks like a real Slackware install. + if [[ ! -d "$SLACKWARE_BASE" || ! -d "$SLACKWARE_BASE/var/log/packages" ]]; then + cat >&2 <<EOF +SLACKWARE_BASE is missing or incomplete: $SLACKWARE_BASE +It must be a LOCAL (non-NFS) full Slackware 15.0 install tree. +Populate it from the mirror with the FULL package set (not minimal, +a minimal base causes false "missing dependency" results): + + mkdir -p "$SLACKWARE_BASE" + for p in "$LOCAL_MIRROR_15"/slackware64/*/*.t?z; do + installpkg --root "$SLACKWARE_BASE" "\$p" + done +EOF + exit 1 + fi + # Guard against the classic mistake: base sitting on the NFS mirror path. + case "$SLACKWARE_BASE" in + "$LOCAL_MIRROR_15"*) + echo "SLACKWARE_BASE must NOT live under LOCAL_MIRROR_15 (NFS). overlayfs over NFS lowerdir is unsupported here." >&2 + exit 1 ;; + esac + + # LOCAL_MIRROR_15: NFS, must be mounted/reachable. + if [[ ! -d "$LOCAL_MIRROR_15" || ! -f "$LOCAL_MIRROR_15/ChangeLog.txt" ]]; then + echo "LOCAL_MIRROR_15 not reachable (NFS mount absent?): $LOCAL_MIRROR_15" >&2 + echo "Expected a Slackware 15.0 mirror with ChangeLog.txt at its root." >&2 + exit 1 + fi + + # SBo roots exist locally. + local r found=0 + for r in "${SBO_TREE_ROOTS[@]}"; do + if [[ -d "$r" ]]; then found=1; else + echo "SBO_TREE_ROOTS path does not exist: $r" >&2 + fi + done + if [[ $found -eq 0 ]]; then + echo "No valid SBo tree root found. Fix SBO_TREE_ROOTS." >&2 + exit 1 + fi +} + +# ============================================================================= +# keep base patched from the mirror (reuse reference logic, point at NFS mirror, +# write to local base). Skipped on --dry-run. +# ============================================================================= +update_base() { + local marker="$SLACKWARE_BASE/last-base-update" + touch "$marker" + local head_now; head_now="$(head -n1 "$LOCAL_MIRROR_15/ChangeLog.txt")" + if [[ "$head_now" == "$(cat "$marker")" ]]; then + echo "Base is up-to-date with the mirror." + return + fi + echo "Patching base from mirror..." + local p + for p in "$LOCAL_MIRROR_15"/patches/packages/*.t?z; do + [[ -e "$p" ]] || continue + if [[ ! -e "$SLACKWARE_BASE/var/lib/pkgtools/packages/$(basename "${p%.*}")" ]]; then + ROOT="$SLACKWARE_BASE" upgradepkg --install-new "$p" + fi + done + echo "$head_now" > "$marker" + echo "Base patched." +} + +# ============================================================================= +# SBo tree lookup. Find the SlackBuild dir for a prog name across roots. +# Echoes the dir path, returns 0 if found, 1 otherwise. +# ============================================================================= +find_slackbuild_dir() { + local prog="$1" root d + for root in "${SBO_TREE_ROOTS[@]}"; do + [[ -d "$root" ]] || continue + # <root>/<category>/<prog> + for d in "$root"/*/"$prog"; do + if [[ -d "$d" && -f "$d/$prog.info" ]]; then + echo "$d"; return 0 + fi + done + done + return 1 +} + +# Category of a SlackBuild dir = its parent dir name. +category_of() { basename "$(dirname "$1")"; } +# Key used in status maps and log filenames. +pkg_key() { echo "$(category_of "$1")/$(basename "$1")"; } + +# Read REQUIRES from a .info, stripped. Echoes space-separated tokens. +read_requires() { + local info="$1" + # shellcheck disable=SC1090 + ( set +u; source "$info"; echo "${REQUIRES:-}" ) +} + +# ============================================================================= +# DEPENDENCY RESOLUTION +# Builds a topological order over the local SBo tree. +# - transitive REQUIRES +# - %README% recorded, not built +# - deps already in base (installed in chroot base) are satisfied, not built +# - deps neither in SBo tree nor installed => UNMET-DEP +# - cycles detected and reported, no infinite loop +# +# Outputs the order into the global array RESOLVED_ORDER (dir paths). +# Records unmet deps into UNMET (prog -> requiring pkg) and cycle errors. +# Returns 1 if any hard resolution failure (unmet dep, cycle) for the target set. +# ============================================================================= +declare -a RESOLVED_ORDER=() +declare -A UNMET=() # prog -> "needed by X" +declare -a CYCLES=() # human-readable cycle descriptions +declare -A HAS_README=() # dir -> 1 if its REQUIRES carried %README% + +# Is a prog already installed in the base tree? (stock 15.0 or otherwise present) +installed_in_base() { + local prog="$1" + # Package db entries look like prog-version-arch-build[ tag] + ls "$SLACKWARE_BASE"/var/log/packages/ 2>/dev/null \ + | grep -qE "^${prog}-[^-]+-[^-]+-[^-]+" +} + +# DFS topo sort with cycle detection. +# visit_state: dir -> 0 visiting (on stack), 1 done +declare -A _vstate=() +_resolve_visit() { + local dir="$1" parent="$2" + local key; key="$(basename "$dir")" + + if [[ "${_vstate[$dir]:-}" == "1" ]]; then return 0; fi + if [[ "${_vstate[$dir]:-}" == "0" ]]; then + CYCLES+=("cycle involving $key (pulled in via $parent)") + return 1 + fi + _vstate["$dir"]=0 + + local info="$dir/$(basename "$dir").info" + local req tok depdir rc=0 + req="$(read_requires "$info")" + for tok in $req; do + if [[ "$tok" == "%README%" ]]; then + HAS_README["$dir"]=1 + continue + fi + if depdir="$(find_slackbuild_dir "$tok")"; then + _resolve_visit "$depdir" "$key" || rc=1 + elif installed_in_base "$tok"; then + : # satisfied by base, nothing to build + else + UNMET["$tok"]="needed by $key" + rc=1 + fi + done + + _vstate["$dir"]=1 + RESOLVED_ORDER+=("$dir") + return $rc +} + +# Resolve a single target dir into RESOLVED_ORDER (deps first, target last). +resolve_target() { + local dir="$1" + RESOLVED_ORDER=() + CYCLES=() + UNMET=() + _vstate=() + _resolve_visit "$dir" "(top)" +} + +# ============================================================================= +# OVERLAY LIFECYCLE (per target). Patterns from overlay-chroot.sh. +# setup_overlay -> echoes the chroot root path, sets CURRENT_OVERLAY. +# teardown_overlay <tmpdir> -> idempotent unwind in correct reverse order. +# ============================================================================= +setup_overlay() { + local tmpdir; tmpdir="$(mktemp -d "$CHROOT_LOCATION"/sbo-bt.XXXXXX)" + mkdir "$tmpdir"/{changes,tmp,chroot} + + # overlayfs: read-only 15.0 base as lowerdir, disposable upper on top. + mount -t overlay overlay \ + -olowerdir="$SLACKWARE_BASE",upperdir="$tmpdir/changes",workdir="$tmpdir/tmp" \ + "$tmpdir/chroot" + + # bind system dirs + mkdir -p "$tmpdir"/changes/{dev,proc,sys} + local i + for i in dev proc sys; do + mount -o bind "/$i" "$tmpdir/chroot/$i" + done + # /dev/pts (sudo/pty) + mkdir -p "$tmpdir"/changes/dev/pts + mount -o bind /dev/pts "$tmpdir/chroot/dev/pts" + # internet + mount -o bind /etc/resolv.conf "$tmpdir/chroot/etc/resolv.conf" + chroot "$tmpdir/chroot" /bin/bash -c "/usr/sbin/update-ca-certificates --fresh >/dev/null 2>&1" || true + # dbus machine-id + touch "$tmpdir/chroot/var/lib/dbus/machine-id" + mount -o bind /var/lib/dbus/machine-id "$tmpdir/chroot/var/lib/dbus/machine-id" + + CURRENT_OVERLAY="$tmpdir" + ACTIVE_MOUNTS+=("$tmpdir") + echo "$tmpdir" +} + +# Idempotent teardown. Safe to call twice, safe mid-abort. +teardown_overlay() { + local tmpdir="$1" + [[ -n "$tmpdir" && -d "$tmpdir" ]] || return 0 + local c="$tmpdir/chroot" + + mountpoint -q "$c/dev/pts" && umount "$c/dev/pts" + local i + for i in dev proc sys; do + mountpoint -q "$c/$i" && umount "$c/$i" + done + mountpoint -q "$c/etc/resolv.conf" && umount "$c/etc/resolv.conf" + mountpoint -q "$c/var/lib/dbus/machine-id" && umount "$c/var/lib/dbus/machine-id" + mountpoint -q "$c" && umount "$c" + + # Remove from ACTIVE_MOUNTS + local n=() m + for m in "${ACTIVE_MOUNTS[@]}"; do [[ "$m" != "$tmpdir" ]] && n+=("$m"); done + ACTIVE_MOUNTS=("${n[@]:-}") + + rm -rf "$tmpdir" +} + +# Trap: unwind every live overlay on abort. Reverse order of creation. +cleanup_trap() { + [[ $WITH_X -eq 1 ]] && xhost -local:hosts >/dev/null 2>&1 || true + local i + for (( i=${#ACTIVE_MOUNTS[@]}-1; i>=0; i-- )); do + teardown_overlay "${ACTIVE_MOUNTS[$i]}" + done +} +trap cleanup_trap EXIT INT TERM + +# ============================================================================= +# BUILD + INSTALL one package inside an existing overlay chroot. +# Args: <chroot-tmpdir> <slackbuild-dir> +# Sets ST_STATUS / ST_REASON / ST_TIME for the package key. +# Returns 0 on SUCCESS, 1 otherwise. +# ============================================================================= +build_one() { + local tmpdir="$1" dir="$2" + local c="$tmpdir/chroot" + local prog cat key + prog="$(basename "$dir")"; cat="$(category_of "$dir")"; key="$cat/$prog" + local logf="$RUN_DIR/${cat}_${prog}.log" + local start; start=$(date +%s) + + # 1. copy SlackBuild dir into the overlay + local workroot="/sbo-work" + mkdir -p "$c$workroot" + cp -a "$dir" "$c$workroot/$prog" + + # %README% reminder carries through to summary + [[ "${HAS_README[$dir]:-}" == "1" ]] && ST_README["$key"]=1 + + # 2-5. download, md5, build, install all run INSIDE the chroot non-interactively. + # The heredoc script writes a status token to a known file we read back out. + # overlayfs note: if a build fails ONLY here and works on bare 15.0, suspect + # an overlayfs sharp edge (rename/whiteout quirks) rather than a real build bug. + local statf="$c$workroot/$prog.status" + chroot "$c" /bin/bash -s <<CHROOT_EOF >>"$logf" 2>&1 +set -uo pipefail +cd "$workroot/$prog" || { echo "BUILD-FAILED: cannot cd"; echo BUILD-FAILED > "$workroot/$prog.status"; exit 1; } +. ./$prog.info + +# Log resolved build context so the overlay never needs to be kept. +echo "===== sbo-batch-test: $prog =====" +echo "PRGNAM=\${PRGNAM:-$prog} VERSION=\${VERSION:-?} BUILD=\${BUILD:-?} TAG=\${TAG:-?}" +echo "uname -m: \$(uname -m) OUTPUT=\${OUTPUT:-/tmp}" +echo "REQUIRES=\${REQUIRES:-}" +echo "=================================" + +# pick arch-specific download/md5 if present (x86_64 VM) +if [ "\$(uname -m)" = "x86_64" ] && [ -n "\${DOWNLOAD_x86_64:-}" ] && [ "\${DOWNLOAD_x86_64}" != "UNSUPPORTED" ] && [ "\${DOWNLOAD_x86_64}" != "UNTESTED" ]; then + DL="\$DOWNLOAD_x86_64"; MD="\$MD5SUM_x86_64" +else + DL="\$DOWNLOAD"; MD="\$MD5SUM" +fi + +# 2. download +for u in \$DL; do + wget -c --tries=3 "\$u" || { echo DOWNLOAD-FAILED > "$workroot/$prog.status"; exit 1; } +done + +# 3. verify md5 +set -- \$MD +for u in \$DL; do + f="\$(basename "\$u")" + want="\$1"; shift + got="\$(md5sum "\$f" | cut -d' ' -f1)" + if [ "\$got" != "\$want" ]; then + echo "MD5 mismatch on \$f: want \$want got \$got" + echo MD5-MISMATCH > "$workroot/$prog.status"; exit 1 + fi +done + +# 4. build non-interactively +chmod +x ./$prog.SlackBuild +if ! ./$prog.SlackBuild; then + echo BUILD-FAILED > "$workroot/$prog.status"; exit 1 +fi + +# 5. installpkg the resulting package (SlackBuilds write to \${OUTPUT:-/tmp}) +out="\${OUTPUT:-/tmp}" +pkg="\$(ls -t "\$out"/${prog}-*.t?z 2>/dev/null | head -n1)" +if [ -z "\$pkg" ]; then + echo "No package produced in \$out" + echo BUILD-FAILED > "$workroot/$prog.status"; exit 1 +fi +if ! installpkg "\$pkg"; then + echo INSTALL-FAILED > "$workroot/$prog.status"; exit 1 +fi +# Log the installed file list (from the package db) so the overlay is disposable. +echo "===== installed files: \$(basename "\$pkg") =====" +pkgname="\$(basename "\$pkg")"; pkgname="\${pkgname%.t?z}" +cat "/var/log/packages/\$pkgname" 2>/dev/null || echo "(package db entry not found: \$pkgname)" +echo "=================================" +echo SUCCESS > "$workroot/$prog.status" +CHROOT_EOF + + local status="BUILD-FAILED" + [[ -f "$statf" ]] && status="$(cat "$statf")" + local end; end=$(date +%s) + ST_TIME["$key"]=$(( end - start )) + ST_STATUS["$key"]="$status" + [[ "$status" == "SUCCESS" ]] && return 0 + ST_REASON["$key"]="see $(basename "$logf")" + return 1 +} + +# ============================================================================= +# Run one target: fresh overlay, build its resolved chain, teardown. +# Args: <target-slackbuild-dir> +# ============================================================================= +run_target() { + local target_dir="$1" + local tkey; tkey="$(pkg_key "$target_dir")" + + echo + echo "=== Target: $tkey ===" + resolve_target "$target_dir" + local resolve_rc=$? + + # Hard resolution failures: mark target and (newly seen) deps, skip building. + if [[ ${#CYCLES[@]} -gt 0 || ${#UNMET[@]} -gt 0 ]]; then + local why="" + if [[ ${#UNMET[@]} -gt 0 ]]; then + local u + for u in "${!UNMET[@]}"; do why+="unmet:$u(${UNMET[$u]}) "; done + fi + [[ ${#CYCLES[@]} -gt 0 ]] && why+="${CYCLES[*]}" + ST_STATUS["$tkey"]="UNMET-DEP" + ST_REASON["$tkey"]="$why" + echo " resolution failed: $why" + return + fi + + if [[ $DRY_RUN -eq 1 ]]; then + echo " build order:" + local d + for d in "${RESOLVED_ORDER[@]}"; do + echo " $(pkg_key "$d")$([[ "${HAS_README[$d]:-}" == 1 ]] && echo " [%README%]")" + echo "$(pkg_key "$d")" >> "$RUN_DIR/build-order.txt" + done + return + fi + + echo " build order: ${RESOLVED_ORDER[*]##*/}" + for d in "${RESOLVED_ORDER[@]}"; do echo "$(pkg_key "$d")" >> "$RUN_DIR/build-order.txt"; done + + local tmpdir; tmpdir="$(setup_overlay)" + + # Build chain in order. On a failure, mark dependents BLOCKED-BY-DEP. + local d failed_progs=() + for d in "${RESOLVED_ORDER[@]}"; do + local key; key="$(pkg_key "$d")" + local prog; prog="$(basename "$d")" + + # already blocked because an earlier dep it needs failed? + if depends_on_failed "$d" failed_progs; then + ST_STATUS["$key"]="BLOCKED-BY-DEP" + ST_REASON["$key"]="blocked by failed dep" + [[ "${HAS_README[$d]:-}" == "1" ]] && ST_README["$key"]=1 + echo " $key: BLOCKED-BY-DEP" + failed_progs+=("$prog") + continue + fi + + echo " building $key ..." + if build_one "$tmpdir" "$d"; then + echo " $key: ${ST_STATUS[$key]} (${ST_TIME[$key]}s)" + else + echo " $key: ${ST_STATUS[$key]} (${ST_TIME[$key]}s)" + failed_progs+=("$prog") + fi + done + + # Overlay is always torn down: the per-package logs capture everything worth + # inspecting (full build/install output, resolved .info env, installed file + # list), so there is no reason to retain the filesystem. + teardown_overlay "$tmpdir" +} + +# Does SlackBuild dir $1 directly require any prog in failed list (nameref $2)? +# ponytail: direct REQUIRES check only; transitive blocking still works because +# this runs in topo order, so a dep's failure propagates up one hop at a time. +depends_on_failed() { + local dir="$1"; local -n failed="$2" + local info="$dir/$(basename "$dir").info" + local req tok f + req="$(read_requires "$info")" + for tok in $req; do + [[ "$tok" == "%README%" ]] && continue + for f in "${failed[@]:-}"; do + [[ "$tok" == "$f" ]] && return 0 + done + done + return 1 +} + +# ============================================================================= +# SUMMARY (screen color + plain summary.log) +# ============================================================================= +print_summary() { + local total=$SECONDS + local succ=0 fail=0 blocked=0 + local summary="$RUN_DIR/summary.log" + + { + echo "sbo-batch-test run summary" + echo "target: $TARGET_ARG" + echo + } > "$summary" + + echo + echo "================ SUMMARY ================" + local key + for key in "${!ST_STATUS[@]}"; do + local st="${ST_STATUS[$key]}" rsn="${ST_REASON[$key]:-}" t="${ST_TIME[$key]:-0}" + local rd=""; [[ "${ST_README[$key]:-}" == "1" ]] && rd=" [%README%]" + local col="$C_YEL" + case "$st" in + SUCCESS) col="$C_GRN"; ((succ++)) ;; + BLOCKED-BY-DEP|UNMET-DEP) col="$C_YEL"; ((blocked++)) ;; + *) col="$C_RED"; ((fail++)) ;; + esac + printf "%s%-30s %-16s%s %s%s (%ss)\n" "$col" "$key" "$st" "$C_RST" "$rsn" "$rd" "$t" + printf "%-30s %-16s %s%s (%ss)\n" "$key" "$st" "$rsn" "$rd" "$t" >> "$summary" + done + echo "----------------------------------------" + printf "%s%d succeeded%s, %s%d failed%s, %s%d blocked%s, total %ss\n" \ + "$C_GRN" "$succ" "$C_RST" "$C_RED" "$fail" "$C_RST" "$C_YEL" "$blocked" "$C_RST" "$total" + echo "logs: $RUN_DIR" + { + echo + echo "$succ succeeded, $fail failed, $blocked blocked, total ${total}s" + echo "logs: $RUN_DIR" + } >> "$summary" +} + +# ============================================================================= +# main +# ============================================================================= +main() { + parse_args "$@" + init_color + validate_env + + RUN_DIR="$LOG_ROOT/$(date +%Y-%m-%d_%H-%M-%S)" + mkdir -p "$RUN_DIR" + : > "$RUN_DIR/build-order.txt" + + [[ $DRY_RUN -eq 0 ]] && update_base + + [[ $WITH_X -eq 1 ]] && { echo "X passthrough on (xhost +local:hosts). Security: allows local connections to your X server."; xhost +local:hosts >/dev/null; } + + # Collect targets. + local -a targets=() + if [[ -d "$TARGET_ARG" ]]; then + # category-folder mode: every subdir with a matching .info is a target + local d prog + for d in "$TARGET_ARG"/*; do + [[ -d "$d" ]] || continue + prog="$(basename "$d")" + [[ -f "$d/$prog.info" ]] && targets+=("$d") + done + if [[ ${#targets[@]} -eq 0 ]]; then + echo "No SlackBuild targets found in folder: $TARGET_ARG" >&2 + exit 1 + fi + else + # single-package mode: resolve the name in the tree + local tdir + if tdir="$(find_slackbuild_dir "$TARGET_ARG")"; then + targets+=("$tdir") + else + echo "Program not found in any SBo tree root: $TARGET_ARG" >&2 + exit 1 + fi + fi + + # TODO: "all" mode and queue/list-file mode plug in here (populate `targets`). + + local t + for t in "${targets[@]}"; do + run_target "$t" + done + + print_summary +} + +main "$@" |
