f91cb8716a
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.
90 lines
3.1 KiB
Python
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())
|