Files
RedBear-OS/local/scripts/verify-durable-source-edits.py
T
vasilito f91cb8716a build-redbear: comprehensive build-system improvements 1+3+4+5+6+7+8
Implements 7 of 8 improvements from local/docs/BUILD-SYSTEM-IMPROVEMENT-PROPOSAL.md:

Improvement 1+5: trap-based stash-and-restore for ALL 9 fork sources
  - Replaces single-relibc stash with a loop over the canonical fork
    inventory (relibc, kernel, base, bootloader, installer, redoxfs,
    libredox, syscall, userutils)
  - Records each stash SHA in a label-keyed map for safe round-tripping
  - Pop stashes in LIFO order matching user-visible stash pop convention
  - Idempotent: re-entry during the same build does not double-stash
  - Reports (does not silently swallow) real git errors during stash push
  - Restored on EXIT regardless of success/failure/signal

Improvement 3: cookbook binary freshness check
  - Replaces existence-only check with BLAKE3-of-src/.rs fingerprint
  - Hash written to target/release/.cookbook-src-fingerprint
  - Rebuild triggers on any source file content change, not just mtime
  - Survives git operations that change content without touching mtime

Improvement 4: strict-by-default uncommitted-edit gate
  - Refuses to build with uncommitted edits in any fork source
  - Catches the 'works on my machine' bug class where pkgar fingerprints
    silently mismatch the committed HEAD
  - Escape hatch: REDBEAR_ALLOW_DIRTY=1 for emergency CI use
  - apply-durable-source-edits.py auto-enables strict mode when
    REDBEAR_BUILD_PHASE is set (set by build-redbear.sh)

Improvement 6: failure-cleanup trap with diagnostics
  - EXIT trap captures last 30 lines of each cookbook log
  - Lists orphaned cook/cargo processes (often root cause of follow-on
    build failures via held locks)
  - Reports per-recipe build state (complete vs incomplete)
  - Preserves diagnostics in REDBEAR_BUILD_STATE_DIR (mktemp -d)
  - REDBEAR_KEEP_BUILD_STATE=1 retains the state dir after exit

Improvement 7: pre-cook offline/upstream consistency
  - Pre-cook now follows the same offline policy as the main build phase
  - REDBEAR_ALLOW_UPSTREAM=1 → offline=false
  - default / REDBEAR_RELEASE → offline=true
  - Pre-cook logs captured into REDBEAR_BUILD_LOGS_DIR for diagnostics
  - Sets REDBEAR_BUILD_PHASE=pre-cook so downstream code can distinguish

Improvement 8: cookbook protection against out-of-band invocations
  - src/bin/repo.rs emits a warning when invoked without
    REDBEAR_CANONICAL_BUILD=1 in the environment
  - The warning lists what is bypassed (cache invalidation, prefix
    rebuild, fingerprint tracking, dirty gate)
  - Non-fatal: CI scripts that manage their own checks are unaffected

Smoke-tested: ./local/scripts/build-redbear.sh redbear-mini now exits 1
when relibc/kernel have uncommitted edits, and proceeds when
REDBEAR_ALLOW_DIRTY=1 is set.
2026-07-10 22:10:33 +03:00

90 lines
3.1 KiB
Python

#!/usr/bin/env python3
"""Detect uncommitted edits in upstream-owned source trees.
Default mode (no flags): emit a human-readable report and exit 0.
Strict mode (--strict, the default when REDBEAR_BUILD_PHASE is set):
emit the same report and exit non-zero if any tree is dirty. This is
how `build-redbear.sh` ensures that any edits to upstream-owned source
trees under `recipes/core/<component>/source/` (which are symlinks into
`local/sources/<component>/`) are detected and either committed or
explicitly overridden via REDBEAR_ALLOW_DIRTY.
The override is --no-strict, which forces exit 0 even when dirty. This
matches the REDBEAR_ALLOW_DIRTY=1 escape hatch in build-redbear.sh.
"""
import argparse
import os
import subprocess
import sys
from pathlib import Path
PROJECT_ROOT = Path(__file__).resolve().parents[2]
UPSTREAM_OWNED = {
"kernel": PROJECT_ROOT / "recipes/core/kernel/source",
"base": PROJECT_ROOT / "recipes/core/base/source",
"relibc": PROJECT_ROOT / "recipes/core/relibc/source",
"bootloader": PROJECT_ROOT / "recipes/core/bootloader/source",
"installer": PROJECT_ROOT / "recipes/core/installer/source",
"redoxfs": PROJECT_ROOT / "recipes/core/redoxfs/source",
}
def git_has_changes(repo: Path) -> tuple[bool, list[str]]:
if not (repo / ".git").exists():
return False, []
proc = subprocess.run(
["git", "status", "--short"],
cwd=repo,
check=False,
capture_output=True,
text=True,
)
lines = [line.rstrip() for line in proc.stdout.splitlines() if line.strip()]
return bool(lines), lines
def main() -> int:
parser = argparse.ArgumentParser(
description="Detect uncommitted edits in upstream-owned Red Bear OS source trees.",
)
# Default to strict when invoked from a canonical build (REDBEAR_BUILD_PHASE
# is set by build-redbear.sh). Default to non-strict for ad-hoc operator use.
canonical = bool(os.environ.get("REDBEAR_BUILD_PHASE"))
parser.add_argument(
"--strict",
action="store_true",
default=canonical,
help="Exit non-zero if any tree is dirty (default when REDBEAR_BUILD_PHASE is set)",
)
parser.add_argument(
"--no-strict",
dest="strict",
action="store_false",
help="Always exit 0, even when dirty (override REDBEAR_BUILD_PHASE default)",
)
args = parser.parse_args()
dirty = False
for label, repo in UPSTREAM_OWNED.items():
has_changes, lines = git_has_changes(repo)
if not has_changes:
continue
dirty = True
print(f"{label}\tDIRTY\t{repo.relative_to(PROJECT_ROOT)}")
for line in lines[:20]:
print(f" {line}")
if len(lines) > 20:
print(f" ... {len(lines) - 20} more")
if args.strict and dirty:
print("", file=sys.stderr)
print("ERROR: uncommitted edits detected in upstream-owned source trees.", file=sys.stderr)
print(" Use --no-strict to allow, or commit your changes.", file=sys.stderr)
return 1
return 0
if __name__ == "__main__":
sys.exit(main())