diff options
Diffstat (limited to '.extras/docs/plans/2026-07-13-docker-test-build.md')
| -rw-r--r-- | .extras/docs/plans/2026-07-13-docker-test-build.md | 1579 |
1 files changed, 0 insertions, 1579 deletions
diff --git a/.extras/docs/plans/2026-07-13-docker-test-build.md b/.extras/docs/plans/2026-07-13-docker-test-build.md deleted file mode 100644 index 6195de9..0000000 --- a/.extras/docs/plans/2026-07-13-docker-test-build.md +++ /dev/null @@ -1,1579 +0,0 @@ -# Docker test-build Implementation Plan - -> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. - -**Goal:** A single bash script `.extras/test-build` that verifies an already-published SBo package still builds cleanly on `-current` or `15.0` inside a throwaway docker container, resolving and building its SBo deps from the local tree, caching built deps per image digest, and reporting per-package status + lint. - -**Architecture:** Host resolves the dep tree from the local SBo tree (logic ported from `sbo-batch-test`), applies current-vs-stable override rules, prints the build order and confirms, then runs one `docker run --rm` that builds deps (or installs them from a host cache) and the target, lints the target, and streams results back. The container is the disposable environment (no overlayfs). Images are built by a separate job and consumed by tag. - -**Tech Stack:** Bash, docker, Slackware `installpkg`/`makepkg`, `sbo-maintainer-tools` (`sbopkglint`). Self-check is plain bash (`test-logic.sh`), no framework. - ---- - -## Reference source - -Port from `/home/danix/Programming/GIT/sbo-batch-tester/sbo-batch-test`. These -functions are lifted with minimal change (they are pure or host-side): - -- `find_slackbuild_dir`, `category_of`, `pkg_key`, `read_requires`, - `version_of`, `installed_in_base` -- resolver: `_resolve_visit`, `resolve_target`, globals `RESOLVED_ORDER`, - `UNMET`, `CYCLES`, `HAS_README`, `_vstate` -- cache: `_cache_ver_of`, `cache_decision`, `cache_path`, `cache_store`, - `cache_label` -- `depends_on_failed`, `print_summary`, `lint_pkg`, `init_color` -- self-check patterns from - `/home/danix/Programming/GIT/sbo-batch-tester/test-logic.sh` - -What is NEW or CHANGED in this repo: - -- overlay lifecycle (`setup_overlay`/`teardown_overlay`/`cleanup_trap`) is - REMOVED. The container replaces it. -- `build_one` is rewritten to run its build steps via `docker run` instead of - `chroot`. -- image + tree selection by version (`--stable`). -- override engine (drop / rename / fetch) applied to the resolved order. -- cache namespaced by image digest. -- config keys differ (see Task 2). - -## File Structure - -- Create: `/home/danix/Programming/GIT/sbo-slackbuilds/.extras/test-build` - (the script, executable) -- Create: - `/home/danix/Programming/GIT/sbo-slackbuilds/.extras/test-build-config.example` - (config template) -- Create: - `/home/danix/Programming/GIT/sbo-slackbuilds/.extras/overrides.example` - (override-rules template) -- Create: - `/home/danix/Programming/GIT/sbo-slackbuilds/.extras/test-logic.sh` - (self-check) - -All work happens in `.extras/`. Run every command from the repo root -`/home/danix/Programming/GIT/sbo-slackbuilds`. - -Note on committing: this repo has a pre-commit hook that runs `sbolint` on -staged packages. `.extras/` files are not packages, but to be safe every -commit in this plan uses `SBOLINT=no git commit ...`. Commits are GPG-signed -automatically; do not disable signing. - ---- - -## Task 1: Scaffold the script + smoke self-check - -**Files:** -- Create: `.extras/test-build` -- Create: `.extras/test-logic.sh` - -- [ ] **Step 1: Write the failing self-check** - -Create `.extras/test-logic.sh`: - -```bash -#!/bin/bash -# -# Logic self-check for .extras/test-build. Covers the pure, host-side parts: -# dependency resolution, override application, unknown-dep -> UNMET, cache -# decisions, and BLOCKED-BY-DEP propagation. No docker. -# -# Run: bash .extras/test-logic.sh -# -set -uo pipefail - -SCRIPT="$(dirname "$0")/test-build" -T=$(mktemp -d) -BASE_DB=$(mktemp -d); mkdir -p "$BASE_DB" - -cleanup() { rm -rf "$T" "$BASE_DB"; } -trap cleanup EXIT - -# Source the script without running main(). Sourcing re-runs the CONFIG block, -# so set test vars AFTER the source. -LIB=$(mktemp) -sed '/^main "\$@"$/d' "$SCRIPT" > "$LIB" -# shellcheck disable=SC1090 -source "$LIB" 2>/dev/null -rm -f "$LIB" - -pass=0; fail=0 -ok() { echo " ok: $1"; ((pass++)); return 0; } -bad() { echo " FAIL: $1"; ((fail++)); return 0; } - -# Placeholder assertion so this file runs before any logic exists. -ok "script sources without executing main" - -echo -echo "$pass passed, $fail failed" -[[ $fail -eq 0 ]] || exit 1 -echo "ALL LOGIC CHECKS PASS" -``` - -- [ ] **Step 2: Run it to verify it fails** - -Run: `bash .extras/test-logic.sh` -Expected: FAIL. `$SCRIPT` does not exist yet, `sed`/`source` on a missing file -errors, no "ALL LOGIC CHECKS PASS". - -- [ ] **Step 3: Write the minimal script skeleton** - -Create `.extras/test-build`: - -```bash -#!/bin/bash -# -# test-build - verify an already-published SBo package still builds on a target -# Slackware version inside a throwaway docker container. Resolves + builds its -# SBo deps from the local tree, caches built deps per image digest, reports -# per-package status and lints the target. -# -# Dependency-resolution, cache, and summary logic are adapted from sbo-batch-test -# (github: danixland). The overlay chroot is replaced by a docker container: the -# container IS the disposable environment, so no overlayfs. -# -# No em dashes in prose by author convention. - -# ============================================================================= -# CONFIG (do not edit here; real values live in the external config file) -# ============================================================================= -SBO_TREE_CURRENT="" -SBO_TREE_STABLE="" -IMAGE_CURRENT="" -IMAGE_STABLE="" -LOG_ROOT="/var/log/sbo-test-build" -PKG_CACHE="" - -TB_CONFIG="${TB_CONFIG:-$HOME/.config/sbo-testbuild/config}" -if [[ -f "$TB_CONFIG" ]]; then - # shellcheck disable=SC1090 - source "$TB_CONFIG" -fi - -TB_OVERRIDES="${TB_OVERRIDES:-$HOME/.config/sbo-testbuild/overrides}" - -# ============================================================================= -set -uo pipefail -# Not -e: a package build failing is a handled outcome, not a script crash. - -main() { - : -} - -main "$@" -``` - -- [ ] **Step 4: Run the self-check to verify it passes** - -Run: `bash .extras/test-logic.sh` -Expected: PASS, ending `ALL LOGIC CHECKS PASS`. - -- [ ] **Step 5: Commit** - -```bash -chmod +x .extras/test-build -git add .extras/test-build .extras/test-logic.sh -SBOLINT=no git commit -m 'test-build: scaffold script + self-check' -``` - ---- - -## Task 2: Config, arg parsing, version selection - -**Files:** -- Modify: `.extras/test-build` -- Create: `.extras/test-build-config.example` -- Test: `.extras/test-logic.sh` - -- [ ] **Step 1: Write the failing test** - -Append to `.extras/test-logic.sh` before the `--- result ---` block (the -`echo; "$pass passed..."` lines at the end): - -```bash -# --- version selection ------------------------------------------------------ -SBO_TREE_CURRENT="/trees/current" -SBO_TREE_STABLE="/trees/stable" -IMAGE_CURRENT="sbo-testbuild:current" -IMAGE_STABLE="sbo-testbuild:15.0" - -VERSION_ID="current"; select_version_paths -[[ "$ACTIVE_TREE" == "/trees/current" ]] && ok "current -> current tree" || bad "current tree wrong: [$ACTIVE_TREE]" -[[ "$ACTIVE_IMAGE" == "sbo-testbuild:current" ]] && ok "current -> current image" || bad "current image wrong: [$ACTIVE_IMAGE]" - -VERSION_ID="15.0"; select_version_paths -[[ "$ACTIVE_TREE" == "/trees/stable" ]] && ok "15.0 -> stable tree" || bad "stable tree wrong: [$ACTIVE_TREE]" -[[ "$ACTIVE_IMAGE" == "sbo-testbuild:15.0" ]] && ok "15.0 -> stable image" || bad "stable image wrong: [$ACTIVE_IMAGE]" -``` - -- [ ] **Step 2: Run test to verify it fails** - -Run: `bash .extras/test-logic.sh` -Expected: FAIL, `select_version_paths: command not found`. - -- [ ] **Step 3: Implement config globals, flags, parse_args, select_version_paths** - -In `.extras/test-build`, replace the `main() { : }` line and everything up to -`main "$@"` with the flags/globals and functions below (keep the CONFIG block -above untouched): - -```bash -# ---- flags / globals -------------------------------------------------------- -USE_COLOR=1 # --no-color or non-TTY disables -DRY_RUN=0 # --dry-run: resolve + print order, do not build -ASSUME_YES=0 # --yes: skip the confirm prompt (still prints the order) -USE_CACHE=1 # --no-cache disables the dep cache for one run -VERSION_ID="current" # "current" | "15.0"; set by --stable -TARGET_ARG="" - -ACTIVE_TREE="" # selected SBo tree (by version) -ACTIVE_IMAGE="" # selected image tag (by version) -RUN_DIR="" # timestamped log dir for this run - -# Status tracking. Keyed by "category/prog". Parallel assoc arrays. -declare -A ST_STATUS=() -declare -A ST_REASON=() -declare -A ST_TIME=() -declare -A ST_README=() - -usage() { - cat <<'EOF' -test-build - verify an SBo package builds on a target Slackware in docker - -USAGE: - test-build [OPTIONS] <program-name> - -OPTIONS: - -h, --help This text. - --stable Target Slackware 15.0 (image + tree). Default is -current. - --dry-run Resolve, apply overrides, print the build order, do not build. - --yes Skip the confirm prompt (the order is still printed first). - --no-cache Rebuild all deps this run, ignore/refresh the cache. - --no-color Disable ANSI color (auto-disabled when stdout is not a TTY). -EOF -} - -parse_args() { - while [[ $# -gt 0 ]]; do - case "$1" in - -h|--help) usage; exit 0 ;; - --stable|15.0) VERSION_ID="15.0"; shift ;; - --dry-run) DRY_RUN=1; shift ;; - --yes) ASSUME_YES=1; shift ;; - --no-cache) USE_CACHE=0; shift ;; - --no-color) USE_COLOR=0; shift ;; - -*) echo "Unknown option: $1" >&2; usage >&2; exit 2 ;; - *) - if [[ -n "$TARGET_ARG" ]]; then - echo "Only one target 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 -} - -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 -} - -# Map VERSION_ID to the active tree + image. No I/O, unit-testable. -select_version_paths() { - if [[ "$VERSION_ID" == "15.0" ]]; then - ACTIVE_TREE="$SBO_TREE_STABLE"; ACTIVE_IMAGE="$IMAGE_STABLE" - else - ACTIVE_TREE="$SBO_TREE_CURRENT"; ACTIVE_IMAGE="$IMAGE_CURRENT" - fi -} - -# require_config: the external config must exist and set the version's paths. -require_config() { - if [[ ! -f "$TB_CONFIG" ]]; then - cat >&2 <<EOF -No config file: $TB_CONFIG -Copy the example and edit it: - - mkdir -p "\$(dirname "$TB_CONFIG")" - cp .extras/test-build-config.example "$TB_CONFIG" - \${EDITOR:-vi} "$TB_CONFIG" -EOF - exit 1 - fi - select_version_paths - if [[ -z "$ACTIVE_TREE" || -z "$ACTIVE_IMAGE" ]]; then - echo "Config $TB_CONFIG is missing the tree/image for version '$VERSION_ID'." >&2 - exit 1 - fi - if [[ ! -d "$ACTIVE_TREE" ]]; then - echo "SBo tree for '$VERSION_ID' does not exist: $ACTIVE_TREE" >&2 - exit 1 - fi -} - -main() { - parse_args "$@" - init_color - require_config -} - -main "$@" -``` - -- [ ] **Step 4: Run test to verify it passes** - -Run: `bash .extras/test-logic.sh` -Expected: PASS (the new version-selection checks included). - -- [ ] **Step 5: Write the config example** - -Create `.extras/test-build-config.example`: - -```bash -# test-build config. Copy to ~/.config/sbo-testbuild/config and edit. -# Override the path with the TB_CONFIG environment variable. - -# Local SBo trees, one per target Slackware version. -SBO_TREE_CURRENT="/home/danix/SBo/current" -SBO_TREE_STABLE="/home/danix/SBo/15.0" - -# Ready image tags (built by a separate job, consumed here by tag). -IMAGE_CURRENT="sbo-testbuild:current" -IMAGE_STABLE="sbo-testbuild:15.0" - -# Where per-run logs land. -LOG_ROOT="/home/danix/.cache/sbo-test-build" - -# Dependency package cache (host dir). Empty disables caching. -# Namespaced internally by image digest, so it self-invalidates on image update -# and keeps current vs 15.0 separate. -PKG_CACHE="/home/danix/.cache/sbo-test-build/pkgcache" -``` - -- [ ] **Step 6: Commit** - -```bash -git add .extras/test-build .extras/test-build-config.example .extras/test-logic.sh -SBOLINT=no git commit -m 'test-build: config, arg parsing, version selection' -``` - ---- - -## Task 3: Port the SBo-tree lookup + resolver + self-check - -**Files:** -- Modify: `.extras/test-build` -- Test: `.extras/test-logic.sh` - -- [ ] **Step 1: Write the failing test** - -Append to `.extras/test-logic.sh` before the result block: - -```bash -# --- resolution ------------------------------------------------------------- -# Fake SBo tree under one category. mk <prog> "<REQUIRES>". -mk() { mkdir -p "$T/cat/$1"; echo "REQUIRES=\"$2\"" > "$T/cat/$1/$1.info"; } -mk c "" -mk b "c" -mk a "b %README%" -mk d "nonexistentpkg" -mk e "f" -mk f "e" -mk g "b" - -SBO_TREE_ROOTS=("$T") # resolver reads this global (set by select in real runs) - -resolve_target "$T/cat/a" -order=""; for x in "${RESOLVED_ORDER[@]}"; do order+="$(basename "$x") "; done -order="${order% }" -[[ "$order" == "c b a" ]] && ok "topo order c b a" || bad "topo order, got: [$order]" -[[ "${HAS_README[$T/cat/a]:-}" == "1" ]] && ok "%README% recorded" || bad "%README% not recorded" -[[ ${#UNMET[@]} -eq 0 ]] && ok "no false unmet" || bad "unexpected unmet" - -resolve_target "$T/cat/d" -[[ ${#UNMET[@]} -eq 1 ]] && ok "unmet-dep caught" || bad "unmet-dep missed" - -resolve_target "$T/cat/e" -[[ ${#CYCLES[@]} -ge 1 ]] && ok "cycle caught" || bad "cycle missed" -``` - -Note: the base-installed check (`installed_in_base`) is exercised in real runs -against the container, not here. In the self-check `BASE_DB` is empty, so a dep -that is neither in the tree nor a known override reads as UNMET (which is the -behavior we test with `d`). - -- [ ] **Step 2: Run test to verify it fails** - -Run: `bash .extras/test-logic.sh` -Expected: FAIL, `resolve_target: command not found`. - -- [ ] **Step 3: Implement lookup + resolver (ported)** - -Add to `.extras/test-build` above `main()`: - -```bash -# ============================================================================= -# SBo tree lookup -# ============================================================================= -# In this tool there is one active tree per run, but the resolver reads an array -# named SBO_TREE_ROOTS so the ported logic and its self-check match sbo-batch-test. -# main() sets SBO_TREE_ROOTS=("$ACTIVE_TREE") after require_config. -declare -a SBO_TREE_ROOTS=() - -find_slackbuild_dir() { - local prog="$1" root d - for root in "${SBO_TREE_ROOTS[@]}"; do - [[ -d "$root" ]] || continue - 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() { basename "$(dirname "$1")"; } -pkg_key() { echo "$(category_of "$1")/$(basename "$1")"; } - -read_requires() { - local info="$1" - # shellcheck disable=SC1090 - ( set +u; source "$info"; echo "${REQUIRES:-}" ) -} - -version_of() { - local dir="$1" - local info="$dir/$(basename "$dir").info" - [[ -f "$info" ]] || return - local v; v="$(grep -m1 '^VERSION=' "$info" | cut -d'"' -f2)" - echo "$v" -} - -# ============================================================================= -# DEPENDENCY RESOLUTION (topo sort + cycle detection, LOCAL tree only) -# ============================================================================= -declare -a RESOLVED_ORDER=() -declare -A UNMET=() -declare -a CYCLES=() -declare -A HAS_README=() -declare -A _vstate=() - -# Is a prog already present in the container base? Overridden at build time to -# consult the image; in resolution we treat "in base" as a callback so the pure -# topo logic stays testable. Default: not in base (self-check has no base db). -installed_in_base() { return 1; } - -_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 - : - else - UNMET["$tok"]="needed by $key" - rc=1 - fi - done - - _vstate["$dir"]=1 - RESOLVED_ORDER+=("$dir") - return $rc -} - -resolve_target() { - local dir="$1" - RESOLVED_ORDER=() - CYCLES=() - UNMET=() - _vstate=() - _resolve_visit "$dir" "(top)" -} -``` - -Update `main()` to set the resolver's tree array after `require_config`: - -```bash -main() { - parse_args "$@" - init_color - require_config - SBO_TREE_ROOTS=("$ACTIVE_TREE") -} -``` - -- [ ] **Step 4: Run test to verify it passes** - -Run: `bash .extras/test-logic.sh` -Expected: PASS (topo order, %README%, unmet, cycle checks green). - -- [ ] **Step 5: Commit** - -```bash -git add .extras/test-build .extras/test-logic.sh -SBOLINT=no git commit -m 'test-build: port SBo lookup + dependency resolver' -``` - ---- - -## Task 4: Override engine (drop / rename / fetch) + UNMET stop - -**Files:** -- Modify: `.extras/test-build` -- Create: `.extras/overrides.example` -- Test: `.extras/test-logic.sh` - -The override file encodes current-vs-stable deltas. It is applied ONLY when -`VERSION_ID == current` (15.0 is the SBo baseline). Rules: - -- `drop: <prog>` remove from the build order (already in -current base) -- `rename: <old> -> <new>` rewrite the dep name before it is looked up -- `fetch: <prog>` mark this dep to come from sbopkg in-container, not the local tree - -- [ ] **Step 1: Write the failing test** - -Append to `.extras/test-logic.sh` before the result block: - -```bash -# --- overrides -------------------------------------------------------------- -OV=$(mktemp) -cat > "$OV" <<'EOF' -# comment ignored -drop: dropme -rename: oldname -> newname -fetch: fetchme -EOF -TB_OVERRIDES="$OV" -load_overrides - -[[ "${OV_DROP[dropme]:-}" == "1" ]] && ok "override drop parsed" || bad "drop not parsed" -[[ "${OV_RENAME[oldname]:-}" == "newname" ]] && ok "override rename parsed" || bad "rename not parsed" -[[ "${OV_FETCH[fetchme]:-}" == "1" ]] && ok "override fetch parsed" || bad "fetch not parsed" - -# rename maps a token -[[ "$(apply_rename oldname)" == "newname" ]] && ok "apply_rename maps" || bad "apply_rename wrong: [$(apply_rename oldname)]" -[[ "$(apply_rename untouched)" == "untouched" ]] && ok "apply_rename passthrough" || bad "apply_rename mangled untouched" - -# drop filters the order (build a fake order of dirs; dropme is removed) -mk dropme "" -mk keepme "" -RESOLVED_ORDER=("$T/cat/dropme" "$T/cat/keepme") -apply_overrides_to_order -kept=""; for x in "${RESOLVED_ORDER[@]}"; do kept+="$(basename "$x") "; done -kept="${kept% }" -[[ "$kept" == "keepme" ]] && ok "drop removes from order" || bad "drop failed, order=[$kept]" -rm -f "$OV" - -# overrides only apply on current; on 15.0 they are a no-op -VERSION_ID="15.0" -TB_OVERRIDES="$OV" # file gone; must not error, must clear maps -load_overrides -[[ ${#OV_DROP[@]} -eq 0 ]] && ok "overrides inert on 15.0" || bad "overrides applied on 15.0" -VERSION_ID="current" -``` - -- [ ] **Step 2: Run test to verify it fails** - -Run: `bash .extras/test-logic.sh` -Expected: FAIL, `load_overrides: command not found`. - -- [ ] **Step 3: Implement the override engine** - -Add to `.extras/test-build` above `main()`: - -```bash -# ============================================================================= -# current-vs-stable overrides. Parsed from $TB_OVERRIDES. Applied only when -# targeting -current (15.0 is the SBo baseline, no deltas). -# ============================================================================= -declare -A OV_DROP=() # prog -> 1 -declare -A OV_RENAME=() # old -> new -declare -A OV_FETCH=() # prog -> 1 - -load_overrides() { - OV_DROP=(); OV_RENAME=(); OV_FETCH=() - # 15.0 is the baseline: no overrides. - [[ "$VERSION_ID" == "15.0" ]] && return - [[ -f "$TB_OVERRIDES" ]] || return - local line kind rest - while IFS= read -r line; do - line="${line%%#*}" # strip comments - line="${line#"${line%%[![:space:]]*}"}" # ltrim - [[ -z "$line" ]] && continue - kind="${line%%:*}"; rest="${line#*:}" - kind="${kind//[[:space:]]/}" - rest="${rest#"${rest%%[![:space:]]*}"}" # ltrim value - case "$kind" in - drop) OV_DROP["${rest//[[:space:]]/}"]=1 ;; - fetch) OV_FETCH["${rest//[[:space:]]/}"]=1 ;; - rename) - # rest is "old -> new" - local old new - old="${rest%%->*}"; new="${rest##*->}" - old="${old//[[:space:]]/}"; new="${new//[[:space:]]/}" - [[ -n "$old" && -n "$new" ]] && OV_RENAME["$old"]="$new" ;; - *) echo "WARN: unknown override rule: $line" >&2 ;; - esac - done < "$TB_OVERRIDES" -} - -# Map a dep token through rename rules (identity if no rule). -apply_rename() { - local tok="$1" - echo "${OV_RENAME[$tok]:-$tok}" -} - -# Remove dropped packages from RESOLVED_ORDER in place. -apply_overrides_to_order() { - local d prog keep=() - for d in "${RESOLVED_ORDER[@]}"; do - prog="$(basename "$d")" - [[ "${OV_DROP[$prog]:-}" == "1" ]] && continue - keep+=("$d") - done - RESOLVED_ORDER=("${keep[@]:-}") -} -``` - -Wire `apply_rename` into the resolver so renamed deps look up under the new -name. In `_resolve_visit`, change the dep-lookup line: - -```bash - for tok in $req; do - if [[ "$tok" == "%README%" ]]; then - HAS_README["$dir"]=1 - continue - fi - tok="$(apply_rename "$tok")" # <-- add this line - if [[ "${OV_FETCH[$tok]:-}" == "1" ]]; then - FETCH_DEPS["$tok"]=1 # satisfied via sbopkg at build time - continue - fi - if depdir="$(find_slackbuild_dir "$tok")"; then - _resolve_visit "$depdir" "$key" || rc=1 - elif installed_in_base "$tok"; then - : - else - UNMET["$tok"]="needed by $key" - rc=1 - fi - done -``` - -Add the `FETCH_DEPS` global with the other resolver globals: - -```bash -declare -A FETCH_DEPS=() # prog -> 1: resolve via sbopkg in-container -``` - -And reset it in `resolve_target`: - -```bash -resolve_target() { - local dir="$1" - RESOLVED_ORDER=() - CYCLES=() - UNMET=() - FETCH_DEPS=() - _vstate=() - _resolve_visit "$dir" "(top)" -} -``` - -Load overrides in `main()` (before any resolve happens, after tree setup): - -```bash -main() { - parse_args "$@" - init_color - require_config - SBO_TREE_ROOTS=("$ACTIVE_TREE") - load_overrides -} -``` - -- [ ] **Step 4: Run test to verify it passes** - -Run: `bash .extras/test-logic.sh` -Expected: PASS (all override checks green). - -- [ ] **Step 5: Write the overrides example** - -Create `.extras/overrides.example`: - -``` -# current-vs-stable dep overrides for test-build. -# Copy to ~/.config/sbo-testbuild/overrides and edit. Applied only when -# targeting -current (15.0 is the SBo baseline). One rule per line. -# -# drop: <prog> dep is in 15.0 but already in the -current base -# rename: <old> -> <new> dep was renamed on -current -# fetch: <prog> dep was removed from the -current tree; pull via sbopkg -# -# Start light: add rules only when a real package needs them. - -# drop: rust -# rename: python3-foo -> foo -# fetch: somelib -``` - -- [ ] **Step 6: Commit** - -```bash -git add .extras/test-build .extras/overrides.example .extras/test-logic.sh -SBOLINT=no git commit -m 'test-build: current-vs-stable override engine' -``` - ---- - -## Task 5: Port the dep cache, namespaced by image digest - -**Files:** -- Modify: `.extras/test-build` -- Test: `.extras/test-logic.sh` - -The cache is the ported sbo-batch-test cache with one change: the cache root -seen by `cache_*` is `$PKG_CACHE/<image-digest>`, computed once per run. A new -digest gives a fresh namespace (old deps ignored), which is the invalidation. - -- [ ] **Step 1: Write the failing test** - -Append to `.extras/test-logic.sh` before the result block: - -```bash -# --- cache ------------------------------------------------------------------ -PKG_CACHE=$(mktemp -d) -CACHE_ROOT="$PKG_CACHE/sha256-deadbeef" # simulate a resolved digest namespace -mkc() { mkdir -p "$CACHE_ROOT/$1/$2"; : > "$CACHE_ROOT/$1/$2/$3"; } - -mkc net libfoo "libfoo-1.1-x86_64-1_danix.txz" -[[ "$(cache_decision net libfoo 1.1)" == "cached" ]] && ok "cache hit on version match" || bad "cache_decision got [$(cache_decision net libfoo 1.1)]" -[[ "$(cache_decision net libfoo 1.2)" == "bump:1.1:1.2" ]] && ok "cache bump reported" || bad "cache bump got [$(cache_decision net libfoo 1.2)]" -[[ "$(cache_decision net libbar 1.0)" == "new" ]] && ok "cache new for absent" || bad "cache new got [$(cache_decision net libbar 1.0)]" - -hit="$(cache_path net libfoo 1.1)" -[[ "$hit" == "$CACHE_ROOT/net/libfoo/libfoo-1.1-x86_64-1_danix.txz" ]] && ok "cache_path returns hit" || bad "cache_path got [$hit]" -[[ -z "$(cache_path net libfoo 9.9)" ]] && ok "cache_path empty on miss" || bad "cache_path not empty on miss" - -srctmp=$(mktemp -d); : > "$srctmp/libfoo-1.2-x86_64-1_danix.txz" -cache_store net libfoo "$srctmp/libfoo-1.2-x86_64-1_danix.txz" -count=$(find "$CACHE_ROOT/net/libfoo" -name '*.t?z' | wc -l) -[[ "$count" -eq 1 ]] && ok "cache_store evicts to one file" || bad "cache_store left $count files" -[[ -e "$CACHE_ROOT/net/libfoo/libfoo-1.2-x86_64-1_danix.txz" ]] && ok "cache_store stored new file" || bad "cache_store did not store" -rm -rf "$srctmp" - -# disabled cache -> new -CACHE_ROOT_SAVE="$CACHE_ROOT"; USE_CACHE=0 -[[ "$(cache_decision net libfoo 1.2)" == "new" ]] && ok "--no-cache disables (new)" || bad "disabled cache got [$(cache_decision net libfoo 1.2)]" -USE_CACHE=1; CACHE_ROOT="$CACHE_ROOT_SAVE" -``` - -- [ ] **Step 2: Run test to verify it fails** - -Run: `bash .extras/test-logic.sh` -Expected: FAIL, `cache_decision: command not found`. - -- [ ] **Step 3: Implement the cache (ported, keyed on CACHE_ROOT + USE_CACHE)** - -Add to `.extras/test-build` above `main()`: - -```bash -# ============================================================================= -# Dependency cache. Layout: $CACHE_ROOT/<cat>/<prog>/<prog>-<ver>-...txz where -# CACHE_ROOT = $PKG_CACHE/<image-digest> (set per run by resolve_cache_root). -# Key is prog+version. --no-cache (USE_CACHE=0) or empty PKG_CACHE disables. -# ============================================================================= -CACHE_ROOT="" # set by resolve_cache_root once the image digest is known - -# True when the cache is usable this run. -_cache_on() { [[ $USE_CACHE -eq 1 && -n "$PKG_CACHE" && -n "$CACHE_ROOT" ]]; } - -_cache_ver_of() { - local prog="$1" base="$2" - base="${base#"$prog"-}" - echo "${base%%-*}" -} - -# cache_decision <cat> <prog> <version> -> cached | bump:OLD:NEW | new -cache_decision() { - local cat="$1" prog="$2" version="$3" - _cache_on || { echo new; return; } - local dir="$CACHE_ROOT/$cat/$prog" - local f newest="" - for f in "$dir/$prog"-*.t?z; do - [[ -e "$f" ]] || continue - [[ -z "$newest" || "$f" -nt "$newest" ]] && newest="$f" - done - [[ -z "$newest" ]] && { echo new; return; } - local have; have="$(_cache_ver_of "$prog" "$(basename "$newest")")" - if [[ "$have" == "$version" ]]; then echo cached; else echo "bump:$have:$version"; fi -} - -cache_path() { - local cat="$1" prog="$2" version="$3" - _cache_on || return - local dir="$CACHE_ROOT/$cat/$prog" - local f newest="" - for f in "$dir/$prog"-*.t?z; do - [[ -e "$f" ]] || continue - [[ -z "$newest" || "$f" -nt "$newest" ]] && newest="$f" - done - [[ -z "$newest" ]] && return - [[ "$(_cache_ver_of "$prog" "$(basename "$newest")")" == "$version" ]] && echo "$newest" -} - -cache_store() { - local cat="$1" prog="$2" src="$3" - _cache_on || return - local dir="$CACHE_ROOT/$cat/$prog" - mkdir -p "$dir" - rm -f "$dir"/*.t?z - cp -a "$src" "$dir/" -} - -cache_label() { - local dir="$1" is_target="$2" - local cat prog ver dec - cat="$(category_of "$dir")"; prog="$(basename "$dir")"; ver="$(version_of "$dir")" - dec="$(cache_decision "$cat" "$prog" "$ver")" - local label - case "$dec" in - cached) label="cached ($ver)" ;; - bump:*) label="rebuild: ${dec#bump:}"; label="${label/:/ -> }" ;; - *) label="build (new)" ;; - esac - if [[ "$is_target" == "1" ]]; then - case "$dec" in - cached) label="build (cached $ver, rebuilt as target)" ;; - esac - echo "target, $label" - else - echo "$label" - fi -} - -# Compute CACHE_ROOT from the image's digest. Falls back to the tag if the -# digest cannot be read (still isolates per image reference). -resolve_cache_root() { - [[ -z "$PKG_CACHE" ]] && { CACHE_ROOT=""; return; } - local digest - digest="$(docker image inspect --format '{{index .Id}}' "$ACTIVE_IMAGE" 2>/dev/null)" - [[ -z "$digest" ]] && digest="tag-${ACTIVE_IMAGE//[^a-zA-Z0-9._-]/_}" - digest="${digest//[^a-zA-Z0-9._-]/_}" - CACHE_ROOT="$PKG_CACHE/$digest" - mkdir -p "$CACHE_ROOT" -} -``` - -- [ ] **Step 4: Run test to verify it passes** - -Run: `bash .extras/test-logic.sh` -Expected: PASS (all cache checks green). - -- [ ] **Step 5: Commit** - -```bash -git add .extras/test-build .extras/test-logic.sh -SBOLINT=no git commit -m 'test-build: port dep cache, namespaced by image digest' -``` - ---- - -## Task 6: BLOCKED-BY-DEP propagation (ported) - -**Files:** -- Modify: `.extras/test-build` -- Test: `.extras/test-logic.sh` - -- [ ] **Step 1: Write the failing test** - -Append to `.extras/test-logic.sh` before the result block: - -```bash -# --- BLOCKED-BY-DEP (depends_on_failed) ------------------------------------- -dead=(b) -if depends_on_failed "$T/cat/g" dead; then ok "g blocked when b dead"; else bad "g should block on b"; fi -if depends_on_failed "$T/cat/a" dead; then ok "a blocked when b dead (direct)"; else bad "a should block on b"; fi - -dead=(c) -if depends_on_failed "$T/cat/a" dead; then bad "a wrongly blocked on c"; else ok "a not directly blocked by c"; fi -if depends_on_failed "$T/cat/b" dead; then ok "b blocked when c dead"; else bad "b should block on c"; fi - -dead=() -if depends_on_failed "$T/cat/a" dead; then bad "a blocked with empty dead"; else ok "no block when nothing dead"; fi - -dead=("%README%") -if depends_on_failed "$T/cat/a" dead; then bad "%README% treated as dep"; else ok "%README% not treated as dep"; fi -``` - -- [ ] **Step 2: Run test to verify it fails** - -Run: `bash .extras/test-logic.sh` -Expected: FAIL, `depends_on_failed: command not found`. - -- [ ] **Step 3: Implement depends_on_failed (ported)** - -Add to `.extras/test-build` above `main()`: - -```bash -# Does SlackBuild dir $1 directly require any prog in the dead list (nameref $2)? -# Direct-requires check only; transitive blocking works because run_target -# iterates in topo order, propagating a failure one hop per package. -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 -} -``` - -- [ ] **Step 4: Run test to verify it passes** - -Run: `bash .extras/test-logic.sh` -Expected: PASS. - -- [ ] **Step 5: Commit** - -```bash -git add .extras/test-build .extras/test-logic.sh -SBOLINT=no git commit -m 'test-build: port BLOCKED-BY-DEP propagation' -``` - ---- - -## Task 7: The container build step (build_one via docker) - -**Files:** -- Modify: `.extras/test-build` - -No self-check here (docker/build is out of the pure-logic reach, same boundary -as sbo-batch-test). Verified by a real run in Task 10. - -- [ ] **Step 1: Implement build_one + lint_pkg** - -Add to `.extras/test-build` above `main()`. - -`lint_pkg` is ported verbatim: - -```bash -# lint_pkg <txz> <logf> -> run sbopkglint on a built package. Host-side, target -# only, fail-soft (skip if absent, never change SUCCESS). -lint_pkg() { - local txz="$1" logf="$2" - if ! command -v sbopkglint >/dev/null 2>&1; then - echo " sbopkglint not installed on host, skipping lint" - return 0 - fi - echo " sbopkglint $(basename "$txz") ..." - echo "===== sbopkglint: $(basename "$txz") =====" >> "$logf" - local out rc - out="$(sbopkglint "$txz" 2>&1)"; rc=$? - printf '%s\n' "$out" >> "$logf" - if [[ $rc -eq 0 ]]; then - echo " lint: ${C_GRN}clean${C_RST}" - else - echo " lint: ${C_RED}findings${C_RST} (see $(basename "$logf")):" - printf '%s\n' "$out" | sed 's/^/ /' - fi -} -``` - -`build_one` runs the same download/md5/build/installpkg steps as sbo-batch-test, -but inside a container via `docker run` instead of `chroot`. The container gets: -the SlackBuild dir mounted read-only, a work dir for output, and (for deps) the -cached .txz mounted in. `sbopkglint` runs host-side on the copied-out package. - -```bash -# build_one <slackbuild-dir> <is_target 0|1> [container-name] -# Runs the build in a throwaway container. Sets ST_STATUS/ST_REASON/ST_TIME. -# Successful builds copy their package to a host workdir; deps are cached and -# the target is linted. Returns 0 on SUCCESS/CACHED, 1 otherwise. -build_one() { - local dir="$1" is_target="${2:-0}" - 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) - local version; version="$(version_of "$dir")" - - [[ "${HAS_README[$dir]:-}" == "1" ]] && ST_README["$key"]=1 - - # Dep with a version-matching cached package: installpkg it into the shared - # dep-package dir; no build. The target never takes this path. - if [[ "$is_target" != "1" ]]; then - local cached; cached="$(cache_path "$cat" "$prog" "$version")" - if [[ -n "$cached" ]]; then - cp -a "$cached" "$DEPS_DIR/" - { - echo "===== test-build: $prog (from cache) =====" - echo "cached package: $(basename "$cached")" - } >> "$logf" - ST_TIME["$key"]=$(( $(date +%s) - start )) - ST_STATUS["$key"]="CACHED" - return 0 - fi - fi - - # Build in a container. Mounts: - # $dir -> /sbo/pkg (ro, the SlackBuild) - # $DEPS_DIR -> /sbo/deps (rw, already-built dep .txz to installpkg first) - # $BUILD_OUT -> /sbo/out (rw, where the built package is copied out) - # The in-container script installs any deps present, then builds the target, - # writes a status token to /sbo/out/$prog.status, and copies the package out. - local statf="$BUILD_OUT/$prog.status" - rm -f "$statf" - - # fetch deps (removed from -current tree): let the container's sbopkg build - # them first. FETCH_DEPS is the set collected during resolution. - local fetch_list="" - local fp - for fp in "${!FETCH_DEPS[@]}"; do fetch_list+="$fp "; done - - docker run --rm \ - -v "$dir":/sbo/pkg:ro \ - -v "$DEPS_DIR":/sbo/deps \ - -v "$BUILD_OUT":/sbo/out \ - -e PROG="$prog" \ - -e FETCH_LIST="$fetch_list" \ - "$ACTIVE_IMAGE" /bin/bash -s >>"$logf" 2>&1 <<'CONTAINER_EOF' -set -uo pipefail -prog="$PROG" -statf="/sbo/out/$prog.status" - -# 0. install already-built dependency packages (order guaranteed by the host). -for d in /sbo/deps/*.t?z; do - [[ -e "$d" ]] || continue - installpkg --terse "$d" || { echo "INSTALL-FAILED (dep $d)"; echo INSTALL-FAILED > "$statf"; exit 1; } -done - -# 0b. fetch-from-SBo deps via sbopkg (removed from the -current tree). -for f in $FETCH_LIST; do - echo "sbopkg-building fetch dep: $f" - sbopkg -B -i "$f" || { echo "BUILD-FAILED (fetch dep $f)"; echo BUILD-FAILED > "$statf"; exit 1; } -done - -# copy the SlackBuild out of the read-only mount so it can write there. -cp -a /sbo/pkg /sbo/build -cd /sbo/build || { echo BUILD-FAILED > "$statf"; exit 1; } -. ./"$prog".info - -export OUTPUT=/sbo/out -mkdir -p "$OUTPUT" - -echo "===== test-build: $prog =====" -echo "PRGNAM=${PRGNAM:-$prog} VERSION=${VERSION:-?} BUILD=${BUILD:-?} TAG=${TAG:-?}" -echo "uname -m: $(uname -m) OUTPUT=$OUTPUT" -echo "REQUIRES=${REQUIRES:-}" -echo "=================================" - -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 - -for u in $DL; do - wget -c --tries=3 "$u" || { echo DOWNLOAD-FAILED > "$statf"; exit 1; } -done - -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 > "$statf"; exit 1 - fi -done - -chmod +x ./"$prog".SlackBuild -if ! ./"$prog".SlackBuild; then - echo BUILD-FAILED > "$statf"; exit 1 -fi - -pkg="$(ls -t "$OUTPUT"/"$prog"-*.t?z 2>/dev/null | head -n1)" -if [ -z "$pkg" ]; then - echo "No package produced in $OUTPUT" - echo BUILD-FAILED > "$statf"; exit 1 -fi -if ! installpkg --terse "$pkg"; then - echo INSTALL-FAILED > "$statf"; exit 1 -fi -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)" -echo "=================================" -echo SUCCESS > "$statf" -CONTAINER_EOF - - local status="BUILD-FAILED" - [[ -f "$statf" ]] && status="$(cat "$statf")" - ST_TIME["$key"]=$(( $(date +%s) - start )) - ST_STATUS["$key"]="$status" - - if [[ "$status" == "SUCCESS" ]]; then - # locate the built package copied to the host workdir - local built newest="" - for built in "$BUILD_OUT/${prog}"-*.t?z; do - [[ -e "$built" ]] || continue - [[ -z "$newest" || "$built" -nt "$newest" ]] && newest="$built" - done - if [[ -n "$newest" ]]; then - if [[ "$is_target" != "1" ]]; then - cache_store "$cat" "$prog" "$newest" - # make the dep available to later builds in this run - cp -a "$newest" "$DEPS_DIR/" - else - lint_pkg "$newest" "$logf" - fi - fi - return 0 - fi - ST_REASON["$key"]="see $(basename "$logf")" - return 1 -} -``` - -- [ ] **Step 2: Sanity-check bash syntax** - -Run: `bash -n .extras/test-build` -Expected: no output (syntax OK). - -- [ ] **Step 3: Run the self-check still passes (no regression)** - -Run: `bash .extras/test-logic.sh` -Expected: PASS (build_one is not exercised, but sourcing must not break). - -- [ ] **Step 4: Commit** - -```bash -git add .extras/test-build -SBOLINT=no git commit -m 'test-build: container build step (build_one via docker)' -``` - ---- - -## Task 8: run_target orchestration + summary - -**Files:** -- Modify: `.extras/test-build` - -- [ ] **Step 1: Implement run_target + print_summary + confirm** - -Add to `.extras/test-build` above `main()`. `print_summary` is ported verbatim -(status set is the same). `run_target` replaces the overlay lifecycle with the -per-run docker workdirs and adds the printed-order + Y/n confirm. - -```bash -# confirm_order: print the resolved order (overrides marked) and ask to proceed. -# --yes skips the prompt but the order is still printed. --dry-run never reaches -# here. Returns 0 to proceed, 1 to abort. -confirm_order() { - local target_dir="$1" - echo " build order (${VERSION_ID}):" - local d - for d in "${RESOLVED_ORDER[@]}"; do - local it=0; [[ "$d" == "$target_dir" ]] && it=1 - local rdm=""; [[ "${HAS_README[$d]:-}" == 1 ]] && rdm=" [%README%]" - printf " %-30s %s%s\n" "$(pkg_key "$d")" "$(cache_label "$d" "$it")" "$rdm" - echo "$(pkg_key "$d")" >> "$RUN_DIR/build-order.txt" - done - # note fetch deps (built via sbopkg in-container, not in the order list) - local fp - for fp in "${!FETCH_DEPS[@]}"; do - printf " %-30s %s\n" "$fp" "fetch (sbopkg in container)" - done - [[ $ASSUME_YES -eq 1 ]] && return 0 - local reply - read -rp " Proceed? [Y/n] " reply - [[ -z "$reply" || "$reply" =~ ^[Yy]$ ]] -} - -# run_target <target-slackbuild-dir> -run_target() { - local target_dir="$1" - local tkey; tkey="$(pkg_key "$target_dir")" - - echo - echo "=== Target: $tkey (${VERSION_ID}) ===" - resolve_target "$target_dir" - apply_overrides_to_order - - # Hard resolution failures: report and stop, do not build. - 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" - echo " add an override rule ($TB_OVERRIDES) and rerun, or fix the tree." >&2 - return - fi - - if [[ $DRY_RUN -eq 1 ]]; then - echo " build order (dry-run):" - local d - for d in "${RESOLVED_ORDER[@]}"; do - local it=0; [[ "$d" == "$target_dir" ]] && it=1 - local rdm=""; [[ "${HAS_README[$d]:-}" == 1 ]] && rdm=" [%README%]" - printf " %-30s %s%s\n" "$(pkg_key "$d")" "$(cache_label "$d" "$it")" "$rdm" - echo "$(pkg_key "$d")" >> "$RUN_DIR/build-order.txt" - done - local fp - for fp in "${!FETCH_DEPS[@]}"; do - printf " %-30s %s\n" "$fp" "fetch (sbopkg in container)" - done - return - fi - - if ! confirm_order "$target_dir"; then - echo " aborted." - ST_STATUS["$tkey"]="ABORTED" - return - fi - - # Per-run docker workdirs (host side, discarded after the run). - DEPS_DIR="$RUN_DIR/deps"; BUILD_OUT="$RUN_DIR/out" - mkdir -p "$DEPS_DIR" "$BUILD_OUT" - - local d failed_progs=() - for d in "${RESOLVED_ORDER[@]}"; do - local key; key="$(pkg_key "$d")" - local prog; prog="$(basename "$d")" - - 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 - - local it=0; [[ "$d" == "$target_dir" ]] && it=1 - echo " building $key ..." - if build_one "$d" "$it"; 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 -} - -# ============================================================================= -# SUMMARY (ported) -# ============================================================================= -print_summary() { - local total=$SECONDS - local succ=0 fail=0 blocked=0 cached=0 - local summary="$RUN_DIR/summary.log" - - { - echo "test-build run summary" - echo "target: $TARGET_ARG version: $VERSION_ID" - 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++)) ;; - CACHED) col="$C_GRN"; ((cached++)) ;; - BLOCKED-BY-DEP|UNMET-DEP|ABORTED) 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, %s%d cached%s, total %ss\n" \ - "$C_GRN" "$succ" "$C_RST" "$C_RED" "$fail" "$C_RST" "$C_YEL" "$blocked" "$C_RST" \ - "$C_GRN" "$cached" "$C_RST" "$total" - echo "logs: $RUN_DIR" - if [[ $fail -eq 0 && $blocked -eq 0 ]]; then - echo "${C_GRN}All green.${C_RST} Safe to build the SBo submission tarball on the host." - fi - { - echo - echo "$succ succeeded, $fail failed, $blocked blocked, $cached cached, total ${total}s" - echo "logs: $RUN_DIR" - } >> "$summary" -} -``` - -Add the two workdir globals near the other globals (top of the flags block): - -```bash -DEPS_DIR="" # per-run host dir of built dep .txz, mounted into the container -BUILD_OUT="" # per-run host dir where built packages are copied out -``` - -- [ ] **Step 2: Sanity-check syntax** - -Run: `bash -n .extras/test-build` -Expected: no output. - -- [ ] **Step 3: Self-check no regression** - -Run: `bash .extras/test-logic.sh` -Expected: PASS. - -- [ ] **Step 4: Commit** - -```bash -git add .extras/test-build -SBOLINT=no git commit -m 'test-build: run_target orchestration + summary + confirm' -``` - ---- - -## Task 9: Wire main(), image presence check, target lookup - -**Files:** -- Modify: `.extras/test-build` - -- [ ] **Step 1: Implement the full main()** - -Replace the current `main()` in `.extras/test-build` with: - -```bash -# Verify the selected image exists locally. It is built by a separate job; this -# script only consumes it. (Later: a local-registry docker pull slots in here.) -require_image() { - if ! docker image inspect "$ACTIVE_IMAGE" >/dev/null 2>&1; then - cat >&2 <<EOF -Image not found locally: $ACTIVE_IMAGE -It is produced by the separate image-builder job (full Slackware $VERSION_ID + -sbo-maintainer-tools + sbopkg), stored on the NAS / local registry. Load or -pull it, then rerun. This script does not build images. -EOF - exit 1 - fi -} - -main() { - parse_args "$@" - init_color - require_config - SBO_TREE_ROOTS=("$ACTIVE_TREE") - load_overrides - - # image + cache namespace (skip the image check on a pure dry-run so the order - # can be inspected without the image present). - if [[ $DRY_RUN -eq 0 ]]; then - command -v docker >/dev/null 2>&1 || { echo "docker not found in PATH." >&2; exit 1; } - require_image - fi - resolve_cache_root - - RUN_DIR="$LOG_ROOT/$(date +%Y-%m-%d_%H-%M-%S)" - mkdir -p "$RUN_DIR" - : > "$RUN_DIR/build-order.txt" - - # Single-package mode: resolve the name in the active tree. - local tdir - if ! tdir="$(find_slackbuild_dir "$TARGET_ARG")"; then - echo "Program not found in the SBo tree ($ACTIVE_TREE): $TARGET_ARG" >&2 - exit 1 - fi - - run_target "$tdir" - print_summary -} -``` - -- [ ] **Step 2: Sanity-check syntax** - -Run: `bash -n .extras/test-build` -Expected: no output. - -- [ ] **Step 3: Dry-run smoke test against the real tree (no docker needed)** - -This needs a real config. If `~/.config/sbo-testbuild/config` is not set up, -create a throwaway one pointing at an existing local SBo tree, then: - -Run: `.extras/test-build --dry-run <some-pkg-in-that-tree>` -Expected: prints `=== Target: ... ===`, a build order, exits 0, no docker call. - -- [ ] **Step 4: Self-check no regression** - -Run: `bash .extras/test-logic.sh` -Expected: PASS. - -- [ ] **Step 5: Commit** - -```bash -git add .extras/test-build -SBOLINT=no git commit -m 'test-build: wire main, image-presence check, target lookup' -``` - ---- - -## Task 10: Real end-to-end build (user-run) + README - -**Files:** -- Modify: `.extras/test-build` (only if the real run surfaces a bug) -- Create: `.extras/README.test-build` (short usage note) - -This task needs a real image and the buildsystem. Per repo rules the throwaway -docker build is Claude-OK, but the IMAGE must exist first (separate job, not yet -built). So this task is: document usage, and record the end-to-end run as a -manual verification step the user performs once an image is available. - -- [ ] **Step 1: Write the usage README** - -Create `.extras/README.test-build`: - -```markdown -# test-build - -Verify an already-published SBo package still builds on a target Slackware -version inside a throwaway docker container. - -## Setup - - mkdir -p ~/.config/sbo-testbuild - cp .extras/test-build-config.example ~/.config/sbo-testbuild/config - cp .extras/overrides.example ~/.config/sbo-testbuild/overrides - $EDITOR ~/.config/sbo-testbuild/config - -Set the two SBo tree paths, the two image tags, LOG_ROOT, and PKG_CACHE. - -The images (`sbo-testbuild:current` / `:15.0`) are FULL Slackware installs plus -`sbo-maintainer-tools` and `sbopkg`, built by a separate job (not this script). -The script errors if the tagged image is not present locally. - -## Usage - - .extras/test-build <pkg> # build on -current (default) - .extras/test-build --stable <pkg> # build on 15.0 - .extras/test-build --dry-run <pkg> # show the build order, no build - .extras/test-build --yes <pkg> # skip the confirm (order still shown) - .extras/test-build --no-cache <pkg> # rebuild all deps this run - -A green run means it is safe to build the SBo submission tarball on the host. - -## current-vs-stable overrides - -`~/.config/sbo-testbuild/overrides` (see `overrides.example`) encodes deltas -that only apply when building on -current: - - drop: <prog> already in the -current base - rename: <old> -> <new> renamed on -current - fetch: <prog> removed from the -current tree; built via sbopkg - -Start light; add rules only when a real package needs them. - -## Self-check - - bash .extras/test-logic.sh -``` - -- [ ] **Step 2: Commit the docs** - -```bash -git add .extras/README.test-build -SBOLINT=no git commit -m 'test-build: usage README' -``` - -- [ ] **Step 3: Manual end-to-end verification (user, once an image exists)** - -Documented for the user to run when a `sbo-testbuild:*` image is available: - -```bash -# pick a small already-published package with a dep, e.g. one already in the repo -.extras/test-build --yes <pkg> -``` - -Expected: build order printed, deps built (or CACHED on a second run), target -built, `sbopkglint` result shown, summary green, `All green.` line printed. -A second run of the same package should show its deps as `CACHED`. - -Record the outcome. If a real bug surfaces (mount path, sbopkg invocation, -package glob), fix it in `.extras/test-build` and re-run; add a self-check case -if the bug was in pure logic. - ---- - -## Self-Review - -Run this checklist against the spec after the plan is written: - -**1. Spec coverage:** -- purpose / one-target-per-run: Task 2 (`parse_args`), Task 9 (`main`) - covered -- two versions, current default: Task 2 (`--stable`, `select_version_paths`) - covered -- resolve local tree + topo: Task 3 - covered -- overrides drop/rename/fetch + UNMET stop: Task 4, stop in Task 8 `run_target` - covered -- external config + overrides file: Task 2, Task 4 - covered -- external image, presence check, no bake: Task 9 `require_image` - covered -- container build (download/md5/build/installpkg): Task 7 - covered -- fetch via sbopkg: Task 7 (`FETCH_LIST` loop) - covered -- dep cache, digest-namespaced, --no-cache, target always fresh: Task 5, wired Task 7/8 - covered -- target lint: Task 7 `lint_pkg`, called in Task 7 build_one - covered -- confirm order + --yes still prints: Task 8 `confirm_order` - covered -- --dry-run: Task 8 `run_target`, Task 9 skips image check - covered -- report + logs (per-pkg, summary, build-order): Task 8 `print_summary`, Task 7 logs - covered -- status values incl CACHED: Task 8 `print_summary` - covered -- self-check parity: Tasks 1-6 - covered - -**2. Placeholder scan:** no TBD/TODO in code steps; every code step shows full -code. The one deferred item (real end-to-end run) is explicit and gated on an -external image, not a placeholder. - -**3. Type consistency:** function + global names match across tasks: -`RESOLVED_ORDER`, `FETCH_DEPS`, `OV_DROP/OV_RENAME/OV_FETCH`, `CACHE_ROOT`, -`ACTIVE_TREE/ACTIVE_IMAGE`, `DEPS_DIR/BUILD_OUT`, `cache_decision/cache_path/ -cache_store/cache_label`, `select_version_paths`, `apply_overrides_to_order`, -`apply_rename`, `load_overrides`, `resolve_cache_root`, `require_image`, -`build_one(dir,is_target)`, `run_target`, `confirm_order`, `print_summary`. -Consistent. - -## Notes on deliberate simplifications - -- Single-package mode only (no category-folder / all / queue modes). This repo - bumps one package at a time. Extension point is `main`'s target lookup. -- `-j` parallelism dropped entirely (was a no-op stub upstream). Builds serial. -- fetch deps are not topo-ordered among themselves; sbopkg resolves their own - deps. Start light per the spec; revisit if a fetch dep needs another fetch dep. -``` - |
