diff --git a/docs/implementation-plan.v1.md b/docs/implementation-plan.v1.md index 2ec8dbe..e5cc1c6 100644 --- a/docs/implementation-plan.v1.md +++ b/docs/implementation-plan.v1.md @@ -190,7 +190,7 @@ scripts/test-unit [--package PATTERN] [--run REGEXP] [--race] scripts/test-integration [--suite NAME|all] [--run-id ID] [--resume] scripts/test-e2e [--scenario NAME|all] [--run-id ID] [--resume] scripts/test-env doctor|coverage|status|logs|collect|recover|reuse|stop|reset|purge|gc ... -scripts/build build|verify (pinned toolchain; host-safe) +scripts/build build|verify|doctor|recover|clean (pinned toolchain; host-safe) scripts/windows/build-test-bundle --run-id ID --config FILE [--ca FILE] scripts/windows/test-host status|prepare|stage|run|collect|stop|reset|recover ``` @@ -208,6 +208,13 @@ exact `.test-runs/` directory, with source-commit and SHA-256 manifest; it refuses replacement rather than overwriting an earlier bundle. This is not a release publisher: signing, version resources, and public checksum publication remain Phase 8 gates. +It must report the exact RVBox toolchain image/cache/artifact footprint and +provide a dry-run-first cleanup that can remove only the ignored `bin/` output, +the exact toolchain image, and its two named Go-cache volumes. Cleanup requires +`--execute` plus interactive confirmation (or CI `--yes`), refuses images used +by containers, never touches `.test-runs/`, and never invokes a global Docker +prune. `scripts/build recover` must rebuild the toolchain image and binaries +after any or all of those owned resources were cleared. The first integration/E2E invocation creates a filesystem-safe random run ID, or validates an explicitly supplied one, and records diff --git a/docs/testing.md b/docs/testing.md index 35e3da4..2d31744 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -21,6 +21,22 @@ Build all supported binaries without installing `make` or Go on the host: scripts/build build ``` +`scripts/build doctor` reports the exact owned builder image, Go cache volumes, +and ignored `bin/` artifact directory. To reclaim space, cleanup is dry-run by +default and never performs a global Docker prune: + +```sh +scripts/build clean --all +scripts/build clean --all --execute --yes +scripts/build recover +``` + +The second command removes only `bin/`, `rvbox-dev-toolchain:latest`, and the +two named RVBox Go-cache volumes. `recover` rebuilds the pinned toolchain and +all binaries from source. It intentionally does not remove `.test-runs/`, which +may contain resumable environments; use the exact-run `scripts/test-env purge` +workflow for those. + For a native Windows run, create the immutable per-run test bundle with the pinned toolchain. Supply a test-specific config whose endpoint and CA path are valid for that run; the command refuses to replace an existing bundle. diff --git a/scripts/build b/scripts/build index c5dc024..117980f 100755 --- a/scripts/build +++ b/scripts/build @@ -3,20 +3,154 @@ set -eu repo_root=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) -target=${1:-build} -shift || true +compose_file=$repo_root/deploy/compose.yaml +toolchain_image=rvbox-dev-toolchain:latest +cache_volumes='rvbox-dev_rvbox-go-build rvbox-dev_rvbox-go-mod' -case $target in - build|verify) ;; - *) - printf '%s\n' 'usage: scripts/build [build|verify]' >&2 - exit 2 - ;; -esac -[ "$#" -eq 0 ] || { - printf '%s\n' 'scripts/build does not accept additional arguments' >&2 - exit 2 +usage() { + cat <<'EOF' +usage: scripts/build build|verify|doctor|recover|clean [options] + + build build supported binaries in the pinned toolchain + verify run the normal pinned verification gate + doctor read-only toolchain/artifact/cache availability report + recover rebuild a missing/broken toolchain image, then build + clean print owned cleanup targets; no deletion without --execute + +clean options: + --artifacts remove only this repository's ignored bin/ directory + --toolchain remove only rvbox-dev-toolchain:latest + --caches remove only the two named RVBox Go cache volumes + --all select all three owned target classes + --execute perform the selected deletion (otherwise dry run) + --yes skip the interactive confirmation; requires --execute + +Run artifacts below .test-runs/ are deliberately not a build-clean target. +Use scripts/test-env purge --run-id ID (or its explicit --all workflow) so a +resumable test environment is never removed by a build cleanup operation. +EOF } -cd "$repo_root" -exec docker compose -f deploy/compose.yaml run --rm toolchain make "$target" +fail() { printf '%s\n' "build: $*" >&2; exit 2; } + +docker_ready() { + docker version >/dev/null 2>&1 || fail "Docker is unavailable; start or repair Docker, then retry" + docker compose -f "$compose_file" version >/dev/null 2>&1 || fail "Docker Compose is unavailable" +} + +ensure_toolchain() { + docker_ready + if ! docker image inspect "$toolchain_image" >/dev/null 2>&1; then + printf '%s\n' "build: toolchain image is absent; rebuilding from $compose_file" >&2 + docker compose -f "$compose_file" build toolchain + fi +} + +run_make() { + target=$1 + ensure_toolchain + cd "$repo_root" + exec docker compose -f "$compose_file" run --rm toolchain make "$target" +} + +size_of() { + path=$1 + if [ -e "$path" ]; then du -sh "$path" 2>/dev/null | awk '{print $1}' || printf '%s' '?'; else printf '%s' '0'; fi +} + +doctor() { + docker_ready + printf 'repository=%s\n' "$repo_root" + if docker image inspect "$toolchain_image" >/dev/null 2>&1; then + printf 'toolchain_image=%s present\n' "$toolchain_image" + else + printf 'toolchain_image=%s absent (scripts/build build will rebuild it)\n' "$toolchain_image" + fi + for volume in $cache_volumes; do + if docker volume inspect "$volume" >/dev/null 2>&1; then + printf 'cache_volume=%s present\n' "$volume" + else + printf 'cache_volume=%s absent (Go will repopulate it)\n' "$volume" + fi + done + printf 'build_artifacts=%s size=%s\n' "$repo_root/bin" "$(size_of "$repo_root/bin")" + printf '%s\n' 'test artifacts are managed separately by scripts/test-env' +} + +confirm() { + [ "$yes" = yes ] && return 0 + [ -t 0 ] || fail "--execute without a terminal requires --yes" + printf '%s' 'Proceed with exactly the printed RVBox cleanup targets? [y/N] ' >&2 + read -r answer + [ "$answer" = y ] || [ "$answer" = Y ] || fail "cleanup cancelled" +} + +clean() { + artifacts=no + toolchain=no + caches=no + execute=no + yes=no + while [ "$#" -gt 0 ]; do + case $1 in + --artifacts) artifacts=yes ;; + --toolchain) toolchain=yes ;; + --caches) caches=yes ;; + --all) artifacts=yes; toolchain=yes; caches=yes ;; + --execute) execute=yes ;; + --yes) yes=yes ;; + --help|-h) usage; return 0 ;; + *) fail "unknown clean option $1" ;; + esac + shift + done + [ "$artifacts" = yes ] || [ "$toolchain" = yes ] || [ "$caches" = yes ] || fail "select at least one clean target" + docker_ready + printf '%s\n' 'RVBox cleanup targets:' + if [ "$artifacts" = yes ]; then + if [ -L "$repo_root/bin" ]; then fail "refusing symlink build-artifact target $repo_root/bin"; fi + printf ' artifact_dir=%s size=%s\n' "$repo_root/bin" "$(size_of "$repo_root/bin")" + fi + if [ "$toolchain" = yes ]; then + if docker image inspect "$toolchain_image" >/dev/null 2>&1; then + users=$(docker ps -a --filter "ancestor=$toolchain_image" --format '{{.ID}}' | wc -l | tr -d ' ') + printf ' toolchain_image=%s containers=%s\n' "$toolchain_image" "$users" + [ "$users" = 0 ] || fail "refusing to remove an image still referenced by containers" + else + printf ' toolchain_image=%s absent\n' "$toolchain_image" + fi + fi + if [ "$caches" = yes ]; then + for volume in $cache_volumes; do + if docker volume inspect "$volume" >/dev/null 2>&1; then + printf ' cache_volume=%s\n' "$volume" + else + printf ' cache_volume=%s absent\n' "$volume" + fi + done + fi + if [ "$execute" != yes ]; then + printf '%s\n' 'dry run only; rerun with --execute (and optionally --yes) to delete these exact targets' + return 0 + fi + confirm + if [ "$artifacts" = yes ] && [ -d "$repo_root/bin" ]; then rm -rf "$repo_root/bin"; fi + if [ "$toolchain" = yes ] && docker image inspect "$toolchain_image" >/dev/null 2>&1; then docker image rm "$toolchain_image"; fi + if [ "$caches" = yes ]; then + for volume in $cache_volumes; do + if docker volume inspect "$volume" >/dev/null 2>&1; then docker volume rm "$volume"; fi + done + fi + printf '%s\n' 'RVBox build cleanup complete; scripts/build recover will recreate the toolchain and build artifacts.' +} + +command=${1:-build} +shift || true +case $command in + build|verify) [ "$#" -eq 0 ] || fail "$command does not accept additional arguments"; run_make "$command" ;; + doctor) [ "$#" -eq 0 ] || fail "doctor does not accept additional arguments"; doctor ;; + recover) [ "$#" -eq 0 ] || fail "recover does not accept additional arguments"; docker_ready; docker compose -f "$compose_file" build toolchain; run_make build ;; + clean) clean "$@" ;; + --help|-h) usage ;; + *) usage >&2; fail "unknown command $command" ;; +esac