From 81f738f7bde97b48a91ab7b0dff500c82e616485 Mon Sep 17 00:00:00 2001 From: vasilito Date: Mon, 27 Jul 2026 12:30:55 +0900 Subject: [PATCH] build system: brush versioning fix + build-redbear.sh versioning/colors/prefix honesty sync-versions: stop stamping vendored-upstream workspaces with the Cat 1 branch version. brush is a vendored upstream (reubeno/brush) whose 12 crates carry their own upstream versions with internal ^ requirements (brush needs brush-parser ^0.4.0, brush-core 0.5.0, ...); rewriting them all to 0.3.1 broke the build. The old git-untracked heuristic stopped catching brush once it was vendored (committed). Add a provenance-based .vendored-upstream opt-out marker (covers the whole class, not just brush) read by should_exclude. build-redbear.sh: - versioning: BUILD_REDBEAR_VERSION (starts 1.0), --version flag, startup banner, auto-bumped by pre-commit hook (bump-build-version.sh) - colored output (TTY + NO_COLOR aware) - prefix rebuild: stop reporting 'rebuilt successfully' on a make no-op; remove derived prefix markers so a stale relibc actually re-cooks into the sysroot; skip the rebuild when only kernel/base (which don't feed the prefix) advanced - record fork source-fingerprints only after a successful build (not before make live), so a failed build no longer marks forks as built Co-Authored-By: Claude Opus 4.8 (1M context) --- local/recipes/shells/brush/.vendored-upstream | 14 ++ local/scripts/build-redbear.sh | 231 ++++++++++++------ local/scripts/bump-build-version.sh | 45 ++++ local/scripts/sync-versions.sh | 32 ++- 4 files changed, 239 insertions(+), 83 deletions(-) create mode 100644 local/recipes/shells/brush/.vendored-upstream create mode 100755 local/scripts/bump-build-version.sh diff --git a/local/recipes/shells/brush/.vendored-upstream b/local/recipes/shells/brush/.vendored-upstream new file mode 100644 index 0000000000..30e482fd06 --- /dev/null +++ b/local/recipes/shells/brush/.vendored-upstream @@ -0,0 +1,14 @@ +# Marker: this recipe's source/ tree is a VENDORED UPSTREAM project, not an +# in-house Red Bear crate. sync-versions.sh reads this file (via should_exclude) +# and skips it, so the Cat 1 branch-version policy never rewrites the upstream +# crate versions. +# +# brush is reubeno/brush @ 897b373e08279f644438fb19438f156fc34647cd, a Cargo +# workspace whose members carry their own upstream versions with internal +# inter-crate requirements (brush 0.4.0, brush-core 0.5.0, brush-parser 0.4.0, +# brush-interactive 0.4.0, brush-builtins 0.2.0, ...). Stamping them all to the +# Red Bear branch version (e.g. 0.3.1) breaks those `^` requirements and the +# build (brush requires brush-parser ^0.4.0, etc.). Red Bear's changes are a +# small set of reviewable Redox-port patches; the upstream versions are kept. +# +# Upstream: https://github.com/reubeno/brush diff --git a/local/scripts/build-redbear.sh b/local/scripts/build-redbear.sh index 12604958d3..50b12200dd 100755 --- a/local/scripts/build-redbear.sh +++ b/local/scripts/build-redbear.sh @@ -4,6 +4,47 @@ set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" +# ── Build-system script version ────────────────────────────── +# Semantic version of this build orchestrator itself, independent of +# REDBEAR_VERSION (which tracks the OS release derived from the git branch). +# Starts at 1.0 and is bumped AUTOMATICALLY on every change by the pre-commit +# git hook (local/scripts/bump-build-version.sh); do not edit the minor by hand. +BUILD_REDBEAR_VERSION="1.1" + +# ── Colorized output ────────────────────────────────── +# Enabled only on a TTY with NO_COLOR unset, so redirected build logs and CI +# output stay plain. Helpers preserve the existing ">>>" prefix convention. +if [ -t 1 ] && [ -z "${NO_COLOR:-}" ]; then + C_RESET=$'\033[0m'; C_BOLD=$'\033[1m' + C_INFO=$'\033[1;36m'; C_OK=$'\033[1;32m' + C_WARN=$'\033[1;33m'; C_ERR=$'\033[1;31m'; C_DIM=$'\033[2m' + C_VER=$'\033[1;35m' # bright magenta — the startup version banner +else + C_RESET=; C_BOLD=; C_INFO=; C_OK=; C_WARN=; C_ERR=; C_DIM=; C_VER= +fi +log() { printf '%s>>> %s%s\n' "$C_INFO" "$*" "$C_RESET"; } +ok() { printf '%s>>> %s%s\n' "$C_OK" "$*" "$C_RESET"; } +warn() { printf '%s>>> WARNING: %s%s\n' "$C_WARN" "$*" "$C_RESET" >&2; } +err() { printf '%s>>> ERROR: %s%s\n' "$C_ERR" "$*" "$C_RESET" >&2; } +hdr() { printf '%s%s%s\n' "$C_BOLD$C_INFO" "$*" "$C_RESET"; } + +# Print full version/build provenance and exit (used by --version). +print_version() { + local git_hash branch + git_hash="$(git -C "$PROJECT_ROOT" rev-parse --short HEAD 2>/dev/null || echo unknown)" + branch="$(git -C "$PROJECT_ROOT" branch --show-current 2>/dev/null || echo unknown)" + hdr "Red Bear OS build system" + printf ' build-redbear.sh : %s\n' "$BUILD_REDBEAR_VERSION" + printf ' OS version : %s\n' "$(git -C "$PROJECT_ROOT" branch --show-current 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' || echo 0.0.0)" + printf ' repo commit : %s (branch %s)\n' "$git_hash" "$branch" + printf ' host : %s\n' "$(uname -n 2>/dev/null || echo unknown)" + printf ' arch : %s\n' "$(uname -m 2>/dev/null || echo unknown)" +} + +# Startup version banner — printed first, highlighted in its own color so the +# running build script's version is always visible at a glance. +printf '%s══════ build-redbear.sh v%s ══════%s\n' "$C_VER" "$BUILD_REDBEAR_VERSION" "$C_RESET" + # Concurrent-build guard. Two builds running at once corrupt each other's # cookbook caches, repo/ artefacts and prefix, and produce non-deterministic # ISOs. Take an exclusive, non-blocking flock on a lockfile; if another build @@ -13,8 +54,8 @@ if [ -z "${REDBEAR_ALLOW_CONCURRENT:-}" ] && command -v flock >/dev/null 2>&1; t REDBEAR_BUILD_LOCK="${TMPDIR:-/tmp}/redbear-build.lock" exec 9>"$REDBEAR_BUILD_LOCK" if ! flock -n 9; then - echo ">>> ERROR: another RedBear build is already running (lock: $REDBEAR_BUILD_LOCK)." >&2 - echo ">>> Wait for it to finish, or set REDBEAR_ALLOW_CONCURRENT=1 to override." >&2 + echo "${C_ERR}>>> ERROR:${C_RESET} another RedBear build is already running (lock: $REDBEAR_BUILD_LOCK)." >&2 + echo "${C_INFO}>>>${C_RESET} Wait for it to finish, or set REDBEAR_ALLOW_CONCURRENT=1 to override." >&2 exit 1 fi fi @@ -161,7 +202,7 @@ redbear_unstash_one() { } redbear_stash_all_dirty_forks() { - echo ">>> Stashing dirty fork sources (restored at exit)..." + echo "${C_INFO}>>>${C_RESET} Stashing dirty fork sources (restored at exit)..." local any_dirty=0 local entry label dir for entry in "${REDBEAR_FORK_SOURCES[@]}"; do @@ -184,7 +225,7 @@ redbear_stash_all_dirty_forks() { redbear_unstash_all_forks() { local reason="${1:-exit}" [ "${#REDBEAR_STASHED_REPOS[@]}" -eq 0 ] && return 0 - echo ">>> Restoring stashed fork sources (reason: $reason)..." + echo "${C_INFO}>>>${C_RESET} Restoring stashed fork sources (reason: $reason)..." local i for ((i=${#REDBEAR_STASHED_REPOS[@]}-1; i>=0; i--)); do local entry="${REDBEAR_STASHED_REPOS[$i]}" @@ -223,7 +264,7 @@ if [ -z "${REDBEAR_VERSION:-}" ]; then REDBEAR_VERSION=$(git -C "$PROJECT_ROOT" branch --show-current 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' || echo "0.0.0") fi export REDBEAR_VERSION -echo ">>> Red Bear OS version: $REDBEAR_VERSION (from git branch)" +echo "${C_INFO}>>>${C_RESET} Red Bear OS version: $REDBEAR_VERSION (from git branch)" # Source .config for build settings, but NEVER auto-set REDBEAR_RELEASE. # Release mode requires explicit REDBEAR_RELEASE= in the environment. @@ -274,6 +315,7 @@ Options: --upstream Allow Redox/upstream recipe source refresh during build --no-cache Force clean rebuild, discarding cached packages -h, --help Show this help + -V, --version Show build-system version/provenance and exit Configs: redbear-full Desktop/graphics target (default) @@ -309,6 +351,10 @@ while [ $# -gt 0 ]; do usage exit 0 ;; + -V|--version) + print_version + exit 0 + ;; -*) echo "Unknown option: $1" >&2 usage >&2 @@ -339,15 +385,16 @@ case "$CONFIG" in ;; esac -echo "========================================" -echo " Red Bear OS Build System" -echo "========================================" -echo "Config: $CONFIG" +hdr "========================================" +hdr " Red Bear OS Build System" +hdr "========================================" +printf 'Config: %s%s%s\n' "$C_BOLD" "$CONFIG" "$C_RESET" echo "Jobs: $JOBS" echo "Apply patches: $APPLY_PATCHES" echo "Upstream: $ALLOW_UPSTREAM" echo "Root: ${PROJECT_ROOT##*/}" -echo "========================================" +echo "Script version: $BUILD_REDBEAR_VERSION" +hdr "========================================" echo "" cd "$PROJECT_ROOT" @@ -394,18 +441,18 @@ if [ "${REDBEAR_ALLOW_WRONG_BRANCH:-0}" != "1" ] && [ -z "${REDBEAR_RELEASE:-}" fi if [ -x "$PROJECT_ROOT/local/scripts/verify-overlay-integrity.sh" ] && [ -z "${REDBEAR_RELEASE:-}" ]; then - echo ">>> Verifying overlay integrity..." + echo "${C_INFO}>>>${C_RESET} Verifying overlay integrity..." if ! "$PROJECT_ROOT/local/scripts/verify-overlay-integrity.sh" --quiet; then - echo ">>> Overlay integrity check FAILED — auto-repairing..." + echo "${C_INFO}>>>${C_RESET} Overlay integrity check FAILED — auto-repairing..." "$PROJECT_ROOT/local/scripts/apply-patches.sh" || true if "$PROJECT_ROOT/local/scripts/verify-overlay-integrity.sh" --quiet; then - echo ">>> Overlay integrity restored." + echo "${C_INFO}>>>${C_RESET} Overlay integrity restored." else echo "WARNING: overlay integrity check still failing after repair." echo " Continuing build — some packages may miscompile." fi else - echo ">>> Overlay integrity OK." + echo "${C_INFO}>>>${C_RESET} Overlay integrity OK." fi echo "" fi @@ -414,7 +461,7 @@ fi # Any WIP directory that shadows a local/recipes/ package must be # replaced with a symlink to the local version. if [ -z "${REDBEAR_RELEASE:-}" ]; then - echo ">>> Enforcing local-over-WIP recipe policy..." + echo "${C_INFO}>>>${C_RESET} Enforcing local-over-WIP recipe policy..." for local_recipe in "$PROJECT_ROOT"/local/recipes/*/*/; do pkg=$(basename "$local_recipe") [ ! -f "$local_recipe/recipe.toml" ] && continue @@ -463,8 +510,8 @@ trap redbear_exit_trap EXIT redbear_dump_failure_diagnostics() { local rc="$1" echo "" - echo "========================================" - echo " BUILD FAILED (exit code: $rc)" + printf '%s========================================%s\n' "$C_ERR" "$C_RESET" + printf '%s BUILD FAILED (exit code: %s)%s\n' "$C_ERR" "$rc" "$C_RESET" echo "========================================" echo "" echo "Diagnostic state preserved at:" @@ -532,13 +579,13 @@ redbear_dump_failure_diagnostics() { } if [ "$APPLY_PATCHES" = "1" ] && [ -z "${REDBEAR_RELEASE:-}" ]; then - echo ">>> Local fork sources already have patches committed. Recipe patches (non-fork recipes) are applied atomically by 'repo fetch' via recipe.toml." + echo "${C_INFO}>>>${C_RESET} Local fork sources already have patches committed. Recipe patches (non-fork recipes) are applied atomically by 'repo fetch' via recipe.toml." elif [ -n "${REDBEAR_RELEASE:-}" ]; then - echo ">>> Release mode: fork sources include all patches; no separate patch step needed." + echo "${C_INFO}>>>${C_RESET} Release mode: fork sources include all patches; no separate patch step needed." fi if [ ! -f "target/release/repo" ]; then - echo ">>> Building cookbook binary (missing)..." + echo "${C_INFO}>>>${C_RESET} Building cookbook binary (missing)..." cargo build --release redbear_record_cookbook_fingerprint else @@ -550,11 +597,11 @@ else [ -f "target/release/.cookbook-src-fingerprint" ] && \ STORED_COOKBOOK_HASH=$(cat "target/release/.cookbook-src-fingerprint") if [ "$CURRENT_COOKBOOK_HASH" != "$STORED_COOKBOOK_HASH" ]; then - echo ">>> Rebuilding cookbook binary (source changed: ${CURRENT_COOKBOOK_HASH:0:12} != ${STORED_COOKBOOK_HASH:0:12})..." + echo "${C_INFO}>>>${C_RESET} Rebuilding cookbook binary (source changed: ${CURRENT_COOKBOOK_HASH:0:12} != ${STORED_COOKBOOK_HASH:0:12})..." cargo build --release redbear_record_cookbook_fingerprint else - echo ">>> Cookbook binary is up to date (${CURRENT_COOKBOOK_HASH:0:12})" + echo "${C_INFO}>>>${C_RESET} Cookbook binary is up to date (${CURRENT_COOKBOOK_HASH:0:12})" fi fi @@ -566,17 +613,17 @@ FW_AMD_DIR="$PROJECT_ROOT/local/firmware/amdgpu" if [ "$CONFIG" = "redbear-full" ]; then if [ -d "$FW_AMD_DIR" ] && [ -n "$(ls -A "$FW_AMD_DIR" 2>/dev/null)" ]; then FW_COUNT=$(ls "$FW_AMD_DIR"/*.bin 2>/dev/null | wc -l) - echo ">>> Found $FW_COUNT AMD firmware blobs" + echo "${C_INFO}>>>${C_RESET} Found $FW_COUNT AMD firmware blobs" else - echo ">>> WARNING: No AMD firmware blobs found." + echo "${C_WARN}>>> WARNING:${C_RESET} No AMD firmware blobs found." echo " Run: ./local/scripts/fetch-firmware.sh" echo " GPU driver will NOT function without firmware." fi echo "" fi -echo ">>> Building Red Bear OS with config: $CONFIG" -echo ">>> Build time varies by config (redbear-mini ~30-60 min, redbear-full ~60-120 min on first build)..." +echo "${C_INFO}>>>${C_RESET} Building Red Bear OS with config: $CONFIG" +echo "${C_INFO}>>>${C_RESET} Build time varies by config (redbear-mini ~30-60 min, redbear-full ~60-120 min on first build)..." # Stale-build prevention: if a low-level source repo has commits newer # than its pkgar, delete that package's pkgar and target dir AND clean @@ -620,9 +667,9 @@ if [ "$NO_CACHE" != "1" ]; then if [ -n "$src_commit" ] && { [ "$src_commit" != "$last_commit" ] || [ "$src_dirty" = "1" ]; }; then if [ "$src_dirty" = "1" ] && [ "$src_commit" = "$last_commit" ]; then - echo ">>> Stale $src detected (working tree has uncommitted changes); invalidating..." + echo "${C_INFO}>>>${C_RESET} Stale $src detected (working tree has uncommitted changes); invalidating..." else - echo ">>> Stale $src detected (source newer than last build); invalidating..." + echo "${C_INFO}>>>${C_RESET} Stale $src detected (source newer than last build); invalidating..." fi rm -f "$PROJECT_ROOT/repo/x86_64-unknown-redox/$src".* find "$PROJECT_ROOT/recipes" -path "*/$src/target" -type d -exec rm -rf {} + 2>/dev/null || true @@ -655,7 +702,7 @@ if [ "$NO_CACHE" != "1" ]; then # Only act when a prior fingerprint exists (skip the first build, which # cooks everything anyway). if [ -n "$last_commit" ] && { [ "$src_commit" != "$last_commit" ] || [ "$src_dirty" = "1" ]; }; then - echo ">>> Stale shared fork $src detected; forcing full userspace relink (correctness)..." + echo "${C_INFO}>>>${C_RESET} Stale shared fork $src detected; forcing full userspace relink (correctness)..." STALE_DETECTED=1 USERSPACE_RUNTIME_STALE=1 [ "$src" = "userutils" ] && rm -f "$PROJECT_ROOT/repo/x86_64-unknown-redox/userutils".* @@ -761,10 +808,10 @@ if [ "$NO_CACHE" != "1" ]; then [ "$relibc_rebuilding" = "1" ] && _boot_abi_changed=1 [ "$USERSPACE_RUNTIME_STALE" = "1" ] && _boot_abi_changed=1 if [ "$_boot_abi_changed" = "1" ]; then - echo ">>> Boot ABI touched (relibc / syscall protocol fork) — relinking ONLY the" - echo ">>> static initfs-critical binaries: $ABI_CRITICAL_PKGS." - echo ">>> Dynamically-linked userspace (Qt/KF6/mesa/sddm/desktop) is untouched —" - echo ">>> shared libc.so resolved at runtime (the Linux glibc model)." + echo "${C_INFO}>>>${C_RESET} Boot ABI touched (relibc / syscall protocol fork) — relinking ONLY the" + echo "${C_INFO}>>>${C_RESET} static initfs-critical binaries: $ABI_CRITICAL_PKGS." + echo "${C_INFO}>>>${C_RESET} Dynamically-linked userspace (Qt/KF6/mesa/sddm/desktop) is untouched —" + echo "${C_INFO}>>>${C_RESET} shared libc.so resolved at runtime (the Linux glibc model)." for _pkg in $ABI_CRITICAL_PKGS; do rm -f "$REPO_DIR/$_pkg".* find "$PROJECT_ROOT/recipes" "$PROJECT_ROOT/local/recipes" \ @@ -802,7 +849,7 @@ if [ -d "$FSTOOLS_DIR" ]; then fi if { [ "$src_ts" != "0" ] && [ "$fstools_ts" != "0" ] && [ "$src_ts" -gt "$fstools_ts" ]; } \ || [ "$src_dirty" = "1" ]; then - echo ">>> Stale host fstools detected ($src newer than build/fstools); rebuilding host installer/redoxfs..." + echo "${C_INFO}>>>${C_RESET} Stale host fstools detected ($src newer than build/fstools); rebuilding host installer/redoxfs..." fstools_stale=1 fi done @@ -831,9 +878,9 @@ if [ -f "$PREFIX_LIBC" ]; then fork_date=$(date -d "@$fork_ts" '+%Y-%m-%d %H:%M:%S' 2>/dev/null || echo "unknown") prefix_date=$(date -d "@$prefix_ts" '+%Y-%m-%d %H:%M:%S' 2>/dev/null || echo "unknown") if [ "$src_dirty" = "1" ]; then - echo ">>> Stale $src detected (working tree has uncommitted changes); prefix will be rebuilt." + echo "${C_INFO}>>>${C_RESET} Stale $src detected (working tree has uncommitted changes); prefix will be rebuilt." else - echo ">>> Stale $src detected (fork $fork_date newer than prefix $prefix_date); prefix will be rebuilt." + echo "${C_INFO}>>>${C_RESET} Stale $src detected (fork $fork_date newer than prefix $prefix_date); prefix will be rebuilt." fi STALE_SRCS+=("$src") STALE_PREFIX=1 @@ -848,34 +895,54 @@ fi # fork sources — without this, recipes link against a stale libc.a and # runtime artefacts (initfs, base daemons) can crash on boot. if [ "$STALE_PREFIX" = "1" ] && [ -z "${REDBEAR_SKIP_PREFIX_REBUILD:-}" ]; then - echo ">>> Auto-rebuilding prefix ($STALE_PREFIX stale fork(s))..." - for src in "${STALE_SRCS[@]}"; do - touch "$PROJECT_ROOT/local/sources/$src" - done - # ARCH and HOST_ARCH are exported at the top of this script. The - # make rule for `prefix` (mk/prefix.mk) derives TARGET from ARCH. - # Pass the explicit `TARGET` override to match how recipe builds - # pass the target triple. - if ! make prefix TARGET="${TARGET:-x86_64-unknown-redox}" 2>&1 | tail -n 20; then - echo ">>> ERROR: prefix rebuild failed. Aborting build to avoid producing broken binaries." >&2 - exit 1 + PREFIX_TARGET="${TARGET:-x86_64-unknown-redox}" + PREFIX_BASE="$PROJECT_ROOT/prefix/$PREFIX_TARGET" + # Only relibc actually feeds the prefix toolchain sysroot (which carries the + # cross compilers + relibc libc.a/headers). kernel/base do NOT contribute to + # the prefix, so a kernel/base-only staleness needs no toolchain rebuild. + _relibc_stale=0 + for _s in "${STALE_SRCS[@]}"; do [ "$_s" = "relibc" ] && _relibc_stale=1; done + + if [ "$_relibc_stale" = "1" ]; then + echo "${C_INFO}>>>${C_RESET} Auto-rebuilding prefix (${#STALE_SRCS[@]} stale fork(s): ${STALE_SRCS[*]})..." + # The `prefix` / `$(PREFIX)/sysroot` make targets have NO prerequisite on + # the relibc SOURCE tree, so once the sysroot directory exists `make + # prefix` reports "nothing to be done" and a relibc change never reaches + # the prefix libc.a. Previously the script then printed "rebuilt + # successfully" and touch'd libc.a — hiding real staleness (the #1 cause + # of undefined-reference link errors). Remove the DERIVED prefix markers + # so the relibc-install recipe re-cooks relibc and the sysroot is + # repopulated from the fresh stage. The (preserved) clang/rust/gcc-install + # markers make this cheap: only relibc is re-cooked. + rm -rf "$PREFIX_BASE/relibc-install" "$PREFIX_BASE/sysroot" + # ARCH and HOST_ARCH are exported at the top of this script. The make + # rule for `prefix` (mk/prefix.mk) derives TARGET from ARCH; pass the + # explicit TARGET override to match how recipe builds pass the triple. + if ! make prefix TARGET="$PREFIX_TARGET" 2>&1 | tail -n 20; then + echo "${C_ERR}>>> ERROR:${C_RESET} prefix rebuild failed. Aborting build to avoid producing broken binaries." >&2 + exit 1 + fi + # A genuine rebuild regenerates libc.a with a fresh mtime (newer than the + # fork commit), so the staleness check will not re-fire. Verify the + # artifact actually appeared — a silent no-op here must never be reported + # as success. + if [ ! -f "$PREFIX_LIBC" ]; then + echo "${C_ERR}>>> ERROR:${C_RESET} prefix rebuild did not produce $PREFIX_LIBC. Aborting." >&2 + exit 1 + fi + echo "${C_INFO}>>>${C_RESET} Prefix rebuilt successfully (relibc refreshed into sysroot)." + else + # kernel/base advanced but neither feeds the prefix toolchain. No rebuild + # is required; re-stamp libc.a so the mtime-based staleness check settles + # instead of firing on every subsequent build. + echo "${C_INFO}>>>${C_RESET} Prefix staleness from '${STALE_SRCS[*]}' only (does not feed the prefix toolchain); no rebuild needed." + touch "$PREFIX_LIBC" fi - echo ">>> Prefix rebuilt successfully." - # Stamp libc.a to now so the staleness check above does not re-fire on - # every subsequent build. `make prefix` extracts a prebuilt toolchain - # tarball and is a no-op when the prefix is already present, so without - # this touch libc.a keeps its old mtime, stays "older" than the fork's - # git commit timestamp, and the (expensive) prefix step runs on every - # build forever. Touching after a successful rebuild means we only rebuild - # again when the fork actually advances past this point. The uncommitted- - # changes (src_dirty) path above is unaffected: it intentionally rebuilds - # whenever the working tree is dirty, regardless of mtime. - touch "$PREFIX_LIBC" STALE_PREFIX=0 fi if [ "$NO_CACHE" = "1" ]; then - echo ">>> Cleaning repo and recipe caches for clean build..." + echo "${C_INFO}>>>${C_RESET} Cleaning repo and recipe caches for clean build..." make repo_clean 2>/dev/null || true rm -rf "$PROJECT_ROOT"/repo find "$PROJECT_ROOT"/local/recipes -maxdepth 4 -name "target" -type d -exec rm -rf {} + 2>/dev/null || true @@ -915,13 +982,13 @@ bash "$PROJECT_ROOT/local/scripts/sync-versions.sh" --check || { _REDOX_TC="$HOME/.redoxer/x86_64-unknown-redox/toolchain" _HOST_TC_DIR="$HOME/.redoxer/x86_64-unknown-linux-gnu" if [ ! -e "$_HOST_TC_DIR/toolchain" ] && [ -d "$_REDOX_TC" ]; then - echo ">>> Provisioning host redoxer toolchain (symlink -> redox toolchain; host toolchains are not downloadable)..." + echo "${C_INFO}>>>${C_RESET} Provisioning host redoxer toolchain (symlink -> redox toolchain; host toolchains are not downloadable)..." mkdir -p "$_HOST_TC_DIR" rm -rf "$_HOST_TC_DIR/toolchain.partial" ln -sfn "$_REDOX_TC" "$_HOST_TC_DIR/toolchain" fi -echo ">>> Pre-cooking critical packages..." +echo "${C_INFO}>>>${C_RESET} Pre-cooking critical packages..." if [ "$CONFIG" = "redbear-full" ]; then # The Qt stack has a strict internal build order (qtbase -> qtshadertools -> # qtdeclarative -> qtsvg -> qtwayland) and downstream consumers (sddm, the @@ -969,13 +1036,11 @@ for pkg in $PRECOOK_PKGS; do fi done -for src in relibc kernel base bootloader installer userutils syscall libredox redox-scheme; do - src_dir="$PROJECT_ROOT/local/sources/$src" - if [ -d "$src_dir/.git" ]; then - git -C "$src_dir" rev-parse HEAD 2>/dev/null > \ - "$PROJECT_ROOT/repo/x86_64-unknown-redox/$src.source-fingerprint" 2>/dev/null || true - fi -done +# NB: fork source-fingerprints used to be recorded HERE, before `make live`. +# That meant a build which failed in make live (or the critical-package gate) +# still stamped every fork as "built", so the next run's staleness detection +# skipped work it should have redone. The fingerprint write now happens only +# after a successful, complete build — see below, past the critical gate. # Per-target env propagation. The cookbook's [build.env] section is not # read by the current parser, so per-target env-var hooks must be set @@ -987,20 +1052,20 @@ done if [ "$CONFIG" = "redbear-bare" ] && [ -z "${REDBEAR_BARE_INITFS:-}" ]; then REDBEAR_BARE_INITFS=1 export REDBEAR_BARE_INITFS - echo ">>> redbear-bare target: REDBEAR_BARE_INITFS=1 (auto-set; minimal 7-daemon initfs)" + echo "${C_INFO}>>>${C_RESET} redbear-bare target: REDBEAR_BARE_INITFS=1 (auto-set; minimal 7-daemon initfs)" fi if [ "${REDBEAR_ALLOW_UPSTREAM:-0}" = "1" ]; then - echo ">>> WARNING: Upstream fetch ENABLED (REDBEAR_ALLOW_UPSTREAM=1)" + echo "${C_WARN}>>> WARNING:${C_RESET} Upstream fetch ENABLED (REDBEAR_ALLOW_UPSTREAM=1)" REPO_OFFLINE=0 COOKBOOK_OFFLINE=false CI=1 REDBEAR_BARE_INITFS="${REDBEAR_BARE_INITFS:-0}" make live "CONFIG_NAME=$CONFIG" "JOBS=$JOBS" 2>&1 elif [ -n "${REDBEAR_RELEASE:-}" ]; then - echo ">>> Release mode: building from local forks (offline)" + echo "${C_INFO}>>>${C_RESET} Release mode: building from local forks (offline)" REPO_OFFLINE=1 COOKBOOK_OFFLINE=true CI=1 REDBEAR_BARE_INITFS="${REDBEAR_BARE_INITFS:-0}" make live "CONFIG_NAME=$CONFIG" "JOBS=$JOBS" 2>&1 elif [ "$ALLOW_UPSTREAM" -eq 1 ]; then - echo ">>> Upstream recipe refresh enabled" + echo "${C_INFO}>>>${C_RESET} Upstream recipe refresh enabled" REPO_OFFLINE=0 COOKBOOK_OFFLINE=false CI=1 REDBEAR_BARE_INITFS="${REDBEAR_BARE_INITFS:-0}" make live "CONFIG_NAME=$CONFIG" "JOBS=$JOBS" 2>&1 else - echo ">>> Upstream recipe refresh disabled (default: offline)" + echo "${C_INFO}>>>${C_RESET} Upstream recipe refresh disabled (default: offline)" REPO_OFFLINE=1 COOKBOOK_OFFLINE=true CI=1 REDBEAR_BARE_INITFS="${REDBEAR_BARE_INITFS:-0}" make live "CONFIG_NAME=$CONFIG" "JOBS=$JOBS" 2>&1 fi @@ -1043,14 +1108,26 @@ if [ -n "$_missing_critical" ]; then exit 1 fi +# Record fork source-fingerprints ONLY now — after make live and the critical- +# package gate have both succeeded. Recording earlier (before make live) stamped +# forks as "built" even when the build later failed, so the next run's staleness +# detection skipped work it should have redone. +for src in relibc kernel base bootloader installer userutils syscall libredox redox-scheme; do + src_dir="$PROJECT_ROOT/local/sources/$src" + if [ -d "$src_dir/.git" ]; then + git -C "$src_dir" rev-parse HEAD 2>/dev/null > \ + "$PROJECT_ROOT/repo/x86_64-unknown-redox/$src.source-fingerprint" 2>/dev/null || true + fi +done + ARCH="${ARCH:-$(uname -m)}" echo "" if [ "$STALE_PREFIX" = "1" ]; then - echo ">>> REMINDER: prefix sysroot is stale. Run 'make prefix' to avoid link errors on next build." + echo "${C_WARN}>>> REMINDER:${C_RESET} prefix sysroot is stale. Run 'make prefix' to avoid link errors on next build." fi -echo "========================================" -echo " Build Complete!" -echo "========================================" +printf '%s========================================%s\n' "$C_OK" "$C_RESET" +printf '%s Build Complete!%s\n' "$C_OK" "$C_RESET" +printf '%s========================================%s\n' "$C_OK" "$C_RESET" echo "ISO: build/$ARCH/$CONFIG.iso" echo "" echo "To run in QEMU:" diff --git a/local/scripts/bump-build-version.sh b/local/scripts/bump-build-version.sh new file mode 100755 index 0000000000..7c9c9bdcc2 --- /dev/null +++ b/local/scripts/bump-build-version.sh @@ -0,0 +1,45 @@ +#!/usr/bin/env bash +# bump-build-version.sh — git pre-commit hook. +# +# Auto-increments the minor of BUILD_REDBEAR_VERSION in +# local/scripts/build-redbear.sh whenever that file is part of the commit. +# The version starts at 1.0 and bumps to 1.1, 1.2, … on every committed change, +# so the version printed at build start always reflects how many times the +# orchestrator has changed. No manual editing of the minor is needed. +# +# Installed via: ./local/scripts/install-git-hooks.sh --all +# Bypass a single commit with: git commit --no-verify +# +# Limitation: re-staging happens at whole-file granularity, so a partial +# (`git add -p`) stage of build-redbear.sh will be promoted to the full file. +# That is the standard trade-off for a re-staging pre-commit hook. + +set -euo pipefail + +REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null)" || exit 0 +FILE="local/scripts/build-redbear.sh" + +# Only act if the build script is actually staged for this commit. +if ! git diff --cached --name-only -- "$FILE" | grep -qx "$FILE"; then + exit 0 +fi + +# Read the version from the STAGED content (what will actually be committed). +cur="$(git show ":$FILE" 2>/dev/null \ + | grep -m1 -oE 'BUILD_REDBEAR_VERSION="[0-9]+\.[0-9]+"' \ + | grep -oE '[0-9]+\.[0-9]+' || true)" +if [ -z "$cur" ]; then + # Version constant not found (unexpected) — do not block the commit. + exit 0 +fi + +major="${cur%%.*}" +minor="${cur#*.}" +new="${major}.$((minor + 1))" + +# Update the working-tree file and re-stage so the commit records the bump. +sed -i -E "s/^BUILD_REDBEAR_VERSION=\"[0-9]+\.[0-9]+\"/BUILD_REDBEAR_VERSION=\"${new}\"/" \ + "$REPO_ROOT/$FILE" +git add "$FILE" +echo "bump-build-version: build-redbear.sh ${cur} -> ${new}" +exit 0 diff --git a/local/scripts/sync-versions.sh b/local/scripts/sync-versions.sh index 2633811c4c..aecf5bd878 100755 --- a/local/scripts/sync-versions.sh +++ b/local/scripts/sync-versions.sh @@ -104,12 +104,32 @@ should_exclude() { [[ "$path" == *"$ex"* ]] && return 0 done - # Skip git-fetched upstream sources. Fetched recipe sources (e.g. brush - # from github) live in gitignored, untracked source/ trees; their crate - # versions belong to upstream, not to the Cat 1 in-house branch-version - # policy. Rewriting them breaks upstream-internal version requirements - # (this happened: brush-shell was forced to 0.3.1 while brush required - # ^0.4.0, breaking the build). In-house crates are tracked trees. + # Vendored-upstream opt-out marker. + # + # A recipe under local/recipes/ whose source/ tree is a VENDORED UPSTREAM + # project (not an in-house Red Bear crate) drops a `.vendored-upstream` + # marker file in its recipe directory. Its crate versions belong to upstream + # and carry internal inter-crate `^` requirements (e.g. brush depends on + # brush-parser ^0.4.0, brush-core 0.5.0), so the Cat 1 branch-version stamp + # (0.3.1) must NOT touch them — doing so breaks the upstream-internal version + # graph and the build. + # + # This supersedes the git-untracked heuristic below for the vendored-but- + # COMMITTED case: brush was originally a git-fetched (untracked) source, so + # the untracked check excluded it; once it was vendored (committed, to pin it + # against upstream drift) the tree became tracked and the check stopped + # matching, silently pulling every brush-* crate into the Cat 1 sync. The + # explicit marker is provenance-based, not tracking-based, so it is correct + # regardless of whether the vendored tree is committed. + local recdir="${path%%/source/*}" + if [ -n "$recdir" ] && [ "$recdir" != "$path" ] && [ -f "$recdir/.vendored-upstream" ]; then + return 0 + fi + + # Skip git-fetched upstream sources. Fetched recipe sources live in + # gitignored, untracked source/ trees; their crate versions belong to + # upstream, not to the Cat 1 in-house branch-version policy. In-house crates + # are tracked trees. (Vendored-but-committed upstreams use the marker above.) if ! git ls-files --error-unmatch "$path" >/dev/null 2>&1; then return 0 fi