a287fe20b9
redbear-ci / check (push) Has been cancelled
Operator decision 2026-08-05: "we will not have gcc 13." The cross toolchain has been GCC 16.1.0 since the port landed, but the build system still defaulted to GCC 13 in three places and nothing recorded the move. Build system: - mk/prefix.mk: GCC_RECIPE?=gcc13 -> gcc16. - mk/prefix.mk: the HOSTED_REDOX package rule derives its names from $(GCC_RECIPE) instead of hardcoding gcc13.pkgar / gcc13.cxx.pkgar. static.redox-os.org publishes no gcc16 package, so that path now fails at download. Deliberate: a loud failure beats a wrong compiler. - build-redbear.sh: refuse to build on a non-GCC-16 toolchain. This is the one that mattered on a Linux host. `make prefix` unpacks upstream's gcc-install.tar.gz, which IS GCC 13.2.0, and no make rule replaces it -- GCC 16 arrives only via install-gcc16-toolchain.sh. A fresh prefix therefore put you silently back on GCC 13, surfacing ~40 minutes later as a kwin C++23 failure. The guard checks all three locations (gcc-install, sysroot, ~/.redoxer/<target>/toolchain -- the last has highest priority) and names the two scripts to run. Docs: - New local/docs/TOOLCHAIN-GCC16.md: why the move (kwin/plasma-workspace need std::ranges::to), build/install/rollback procedure, the three locations that must agree, the tree-wide -std=gnu17 and -fno-hardened consequences, and the open libstdc++ float16-formatter gap that currently blocks the kwin link. - CHANGELOG, docs/README.md index and state summary, and the stale claim in NATIVE-TOOLCHAIN-WORKSTREAM.md that gcc13 "still builds the GCC 13 cross toolchain". Also corrects stale references found while auditing: - AGENTS.md described prefix/ as "Clang/LLVM"; it is GCC 16.1.0 + LLVM + Rust. - AGENTS.md cited local/reference/linux-7.0/ (tree is linux-7.1). - AGENTS.md's durable-patching example cited a mesa patch that does not exist; replaced with one wired in recipe.toml. (The first replacement used patch 03, which the changelog records as orphaned -- it targets a file removed from Mesa 26.1.4 upstream.) native_bootstrap.sh still installs gcc13/gcc13.cxx packages inside a Redox VM. Left alone: that is the deferred gcc-native workstream and no gcc16 package is published for Redox to point it at. Verified: bash -n clean; make -n prefix parses; guard accepts the current 16.1.0 toolchain in all three locations and rejects a stubbed 13.2.0. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1362 lines
68 KiB
Bash
Executable File
1362 lines
68 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
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.13"
|
|
|
|
# ── 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
|
|
# already holds it, refuse rather than silently racing. Set REDBEAR_ALLOW_
|
|
# CONCURRENT=1 to override (e.g. builds targeting isolated output dirs).
|
|
if [ -z "${REDBEAR_ALLOW_CONCURRENT:-}" ] && command -v flock >/dev/null 2>&1; then
|
|
REDBEAR_BUILD_LOCK="${TMPDIR:-/tmp}/redbear-build.lock"
|
|
exec 9>"$REDBEAR_BUILD_LOCK"
|
|
if ! flock -n 9; then
|
|
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
|
|
|
|
source "$SCRIPT_DIR/lib/relibc-surface.sh"
|
|
|
|
# =============================================================================
|
|
# Canonical-build markers. Set ONLY by build-redbear.sh itself.
|
|
# Downstream tools (cookbook, preflight) read REDBEAR_CANONICAL_BUILD to know
|
|
# they are running inside the canonical pipeline. Manual `repo cook` invocations
|
|
# outside this script will NOT have this set, which enables:
|
|
# - gentle "you are off the canonical path" warnings
|
|
# - strict-by-default dirty-source gates
|
|
# =============================================================================
|
|
export REDBEAR_CANONICAL_BUILD=1
|
|
export REDBEAR_BUILD_HOST="$(uname -n 2>/dev/null || echo unknown)"
|
|
export REDBEAR_BUILD_START_EPOCH="$(date +%s)"
|
|
export REDBEAR_BUILD_STATE_DIR="$(mktemp -d -t redbear-build-state.XXXXXX)"
|
|
# Diagnostic capture: every cookbook log goes here in addition to /tmp
|
|
REDBEAR_BUILD_LOGS_DIR="$REDBEAR_BUILD_STATE_DIR/logs"
|
|
mkdir -p "$REDBEAR_BUILD_LOGS_DIR"
|
|
|
|
declare -ga REDBEAR_FORK_SOURCES=(
|
|
"relibc:$PROJECT_ROOT/local/sources/relibc"
|
|
"kernel:$PROJECT_ROOT/local/sources/kernel"
|
|
"base:$PROJECT_ROOT/local/sources/base"
|
|
"bootloader:$PROJECT_ROOT/local/sources/bootloader"
|
|
"installer:$PROJECT_ROOT/local/sources/installer"
|
|
"redoxfs:$PROJECT_ROOT/local/sources/redoxfs"
|
|
"libredox:$PROJECT_ROOT/local/sources/libredox"
|
|
"syscall:$PROJECT_ROOT/local/sources/syscall"
|
|
"userutils:$PROJECT_ROOT/local/sources/userutils"
|
|
)
|
|
|
|
redbear_is_fork_dirty() {
|
|
local dir="$1"
|
|
if [ ! -d "$dir/.git" ]; then
|
|
return 0
|
|
fi
|
|
git -C "$dir" diff --quiet HEAD 2>/dev/null || return 0
|
|
git -C "$dir" diff --cached --quiet HEAD 2>/dev/null || return 0
|
|
if [ -n "$(git -C "$dir" ls-files --others --exclude-standard 2>/dev/null)" ]; then
|
|
return 0
|
|
fi
|
|
return 1
|
|
}
|
|
|
|
# Per AGENTS.md § "BRANCH AND SUBMODULE POLICY (ABSOLUTE)": each Red Bear
|
|
# fork worktree MUST be checked out on the canonical "submodule/<name>"
|
|
# branch. This gate prevents drift from the canonical branch topology.
|
|
redbear_check_fork_branches() {
|
|
local err=0
|
|
local wrong=()
|
|
for entry in "${REDBEAR_FORK_SOURCES[@]}"; do
|
|
local label="${entry%%:*}"
|
|
local dir="${entry#*:}"
|
|
[ -d "$dir" ] || continue
|
|
local cur_branch
|
|
cur_branch=$(git -C "$dir" branch --show-current 2>/dev/null) || continue
|
|
local expected="submodule/${label}"
|
|
if [ "$cur_branch" != "$expected" ]; then
|
|
wrong+=("$label (got '$cur_branch', expected '$expected')")
|
|
err=1
|
|
fi
|
|
done
|
|
if [ "$err" != "0" ]; then
|
|
echo "========================================" >&2
|
|
echo " REFUSING TO BUILD — FORKS NOT ON CANONICAL BRANCHES" >&2
|
|
echo "========================================" >&2
|
|
echo "" >&2
|
|
echo "Per AGENTS.md § 'BRANCH AND SUBMODULE POLICY', each Red Bear" >&2
|
|
echo "fork worktree must be checked out on its canonical 'submodule/<name>'" >&2
|
|
echo "branch. The following forks are not:" >&2
|
|
echo "" >&2
|
|
for w in "${wrong[@]}"; do
|
|
echo " - $w" >&2
|
|
done
|
|
echo "" >&2
|
|
echo "Fix with (from the parent repo):" >&2
|
|
echo " for f in base bootloader installer kernel libredox redoxfs relibc syscall userutils; do" >&2
|
|
echo " (cd \"local/sources/\$f\" && git branch -m HEAD submodule/\$f 2>/dev/null || true)" >&2
|
|
echo " done" >&2
|
|
echo " ./local/scripts/push-fork-branches.sh" >&2
|
|
echo "" >&2
|
|
echo "Override (emergency only): REDBEAR_ALLOW_WRONG_BRANCH=1 $0 $*" >&2
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
# Cookbook binary fingerprint: BLAKE3-hash (via sha256sum on this system)
|
|
# of all .rs files in src/. Used to detect when the cookbook needs to be
|
|
# rebuilt. The hash is written to target/release/.cookbook-src-fingerprint
|
|
# after each successful build. Hashing every .rs file (sorted by path
|
|
# for determinism) gives content-stable fingerprints: git operations that
|
|
# change content without touching mtime (checkout, bisect, etc.) still
|
|
# trigger a rebuild.
|
|
redbear_compute_cookbook_fingerprint() {
|
|
if [ -d "$PROJECT_ROOT/src" ]; then
|
|
find "$PROJECT_ROOT/src" -name "*.rs" -type f -print0 2>/dev/null \
|
|
| sort -z \
|
|
| xargs -0 sha256sum 2>/dev/null \
|
|
| sha256sum \
|
|
| cut -d' ' -f1
|
|
else
|
|
echo "no-src-dir"
|
|
fi
|
|
}
|
|
|
|
redbear_record_cookbook_fingerprint() {
|
|
mkdir -p "$PROJECT_ROOT/target/release"
|
|
redbear_compute_cookbook_fingerprint > "$PROJECT_ROOT/target/release/.cookbook-src-fingerprint"
|
|
}
|
|
|
|
# ── Pre-build cargo check sweep (--check-sweep) ──────────────────────
|
|
# Fast type/borrow check of the target's Rust sources BEFORE the expensive
|
|
# cook/prefix cycle, so ALL compile errors surface at once instead of one per
|
|
# full build. Uses the redoxer toolchain's cargo with `cargo check` (which does
|
|
# no linking, so it needs neither relibc.a nor the C linker) against the redox
|
|
# target. Scope: the fork sources (every target builds these) plus the local
|
|
# Rust recipes that are in the config being built.
|
|
redbear_resolve_config_packages() {
|
|
python3 - "$PROJECT_ROOT" "$1" <<'PYEOF' 2>/dev/null || true
|
|
import sys, tomllib
|
|
from pathlib import Path
|
|
root = Path(sys.argv[1]); cfg = sys.argv[2]
|
|
def resolve(p, seen=None):
|
|
seen = seen or set(); p = p.resolve()
|
|
if p in seen or not p.exists():
|
|
return {}
|
|
seen.add(p); c = tomllib.loads(p.read_text()); pkgs = dict(c.get("packages", {}))
|
|
for inc in c.get("include", []):
|
|
ip = p.parent / inc
|
|
if ip.exists():
|
|
m = resolve(ip, seen); m.update(pkgs); pkgs = m
|
|
return pkgs
|
|
for name in sorted(resolve(root / "config" / f"{cfg}.toml")):
|
|
print(name)
|
|
PYEOF
|
|
}
|
|
|
|
redbear_check_sweep() {
|
|
local target="${TARGET:-x86_64-unknown-redox}"
|
|
local tc="$HOME/.redoxer/$target/toolchain"
|
|
if [ ! -x "$tc/bin/cargo" ]; then
|
|
warn "check-sweep: redoxer toolchain not found at $tc/bin/cargo — skipping (run a full build once to provision it)"
|
|
return 0
|
|
fi
|
|
log "Check-sweep: cargo check --target $target (pre-build type/borrow check)"
|
|
local -a manifests=() names=()
|
|
local d
|
|
# Forks that do NOT build for the redox userspace target — checking them with
|
|
# --target x86_64-unknown-redox is meaningless (false positives). bootloader
|
|
# is a bare-metal/UEFI crate built for its own custom targets
|
|
# (targets/*.json), so skip it here.
|
|
local CHECK_SKIP_FORKS=" bootloader "
|
|
# 1) Fork sources — every target builds these; the recurring compile-error
|
|
# source (relibc, base, ...). A relibc error here would otherwise only
|
|
# surface during the prefix rebuild after a multi-minute cook.
|
|
for d in "$PROJECT_ROOT"/local/sources/*/; do
|
|
[ -f "${d}Cargo.toml" ] || continue
|
|
local fork_name; fork_name="$(basename "$d")"
|
|
case "$CHECK_SKIP_FORKS" in *" $fork_name "*)
|
|
log " (skip $fork_name — not built for $target)"; continue ;;
|
|
esac
|
|
manifests+=("${d}Cargo.toml"); names+=("$fork_name")
|
|
done
|
|
# 2) Local Rust recipes that are in THIS config (target-scoped — a mini build
|
|
# does not check graphics recipes).
|
|
local pkg src
|
|
while IFS= read -r pkg; do
|
|
[ -n "$pkg" ] || continue
|
|
for src in "$PROJECT_ROOT"/local/recipes/*/"$pkg"/source/Cargo.toml; do
|
|
[ -f "$src" ] || continue
|
|
manifests+=("$src"); names+=("$pkg")
|
|
done
|
|
done < <(redbear_resolve_config_packages "$CONFIG")
|
|
local i failed=0 checked=0
|
|
local -a failed_names=()
|
|
for i in "${!manifests[@]}"; do
|
|
local n="${names[$i]}"
|
|
checked=$((checked + 1))
|
|
local logf="$REDBEAR_BUILD_LOGS_DIR/check-sweep-$n.log"
|
|
if PATH="$tc/bin:$PATH" RUSTUP_TOOLCHAIN= "$tc/bin/cargo" check \
|
|
--manifest-path "${manifests[$i]}" --target "$target" --offline \
|
|
>"$logf" 2>&1; then
|
|
ok " check OK: $n"
|
|
else
|
|
err "check FAILED: $n"
|
|
grep -E "^error(\[|:)|could not compile" "$logf" 2>/dev/null | sed 's/^/ /' | head -12 >&2
|
|
failed=$((failed + 1)); failed_names+=("$n")
|
|
fi
|
|
done
|
|
echo ""
|
|
if [ "$failed" -gt 0 ]; then
|
|
err "Check-sweep: $failed/$checked package(s) FAILED cargo check: ${failed_names[*]}"
|
|
err "Full logs under $REDBEAR_BUILD_LOGS_DIR/check-sweep-*.log — fix these before building."
|
|
return 1
|
|
fi
|
|
ok "Check-sweep passed: all $checked package(s) type-check clean."
|
|
return 0
|
|
}
|
|
|
|
# Derive Red Bear OS version from the current git branch name.
|
|
# Branch "0.2.4" → REDBEAR_VERSION="0.2.4".
|
|
# Exported so the redbear-release recipe and other consumers can use it.
|
|
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 "${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.
|
|
if [ -f "$PROJECT_ROOT/.config" ]; then
|
|
while IFS= read -r line; do
|
|
line="${line%%#*}"
|
|
line=$(echo "$line" | xargs)
|
|
[ -z "$line" ] && continue
|
|
if [[ "$line" == *"?="* ]]; then
|
|
key="${line%%\?=*}"
|
|
value="${line#*\?=}"
|
|
elif [[ "$line" == *"="* ]]; then
|
|
key="${line%%=*}"
|
|
value="${line#*=}"
|
|
else
|
|
continue
|
|
fi
|
|
key=$(echo "$key" | xargs)
|
|
value=$(echo "$value" | xargs)
|
|
[ -z "$key" ] && continue
|
|
# Skip REDBEAR_RELEASE — dev builds must not use release mode
|
|
[ "$key" = "REDBEAR_RELEASE" ] && continue
|
|
# Only set if not already set in environment
|
|
[ -n "${!key:-}" ] || export "$key=$value"
|
|
done < "$PROJECT_ROOT/.config"
|
|
fi
|
|
|
|
# ── Operator knobs ─────────────────────────────────────────────
|
|
# The common knobs below have CLI flags (the canonical interface); the matching
|
|
# REDBEAR_*/JOBS environment variables remain DEPRECATED fallback defaults so
|
|
# older automation keeps working. Rarer escape hatches stay env-only (listed in
|
|
# usage()). Resolved values are re-exported after the parser so this script's
|
|
# gates and the sub-scripts observe the flag-provided values.
|
|
CONFIG="redbear-full"
|
|
JOBS="${JOBS:-$(nproc)}"
|
|
# Dep-level parallel recipe cooking: cook this many independent (same-level)
|
|
# recipes at once.
|
|
#
|
|
# DEFAULT IS 1 (SERIAL) ON PURPOSE. Recipes are NOT isolated from each other:
|
|
# redbear_qt_ensure_dep_sysroots (local/scripts/lib/qt-sysroot.sh) repairs OTHER
|
|
# recipes' sysroots, so a cook can relink the very include/ or lib/ directory a
|
|
# concurrent cook is compiling against. That produced failures that moved
|
|
# between source files from run to run -- the signature of a race, not a bug:
|
|
#
|
|
# kirigami: cstdio:47: fatal error: .../qtdeclarative/.../sysroot/include/
|
|
# stdio.h: No such file or directory
|
|
# (build 59 failed in KirigamiTemplates, build 61 in KirigamiPrivateplugin)
|
|
#
|
|
# Making that relink atomic removed one window, but the underlying sharing
|
|
# remains: recipes write into each other's sysroots by design. Until that
|
|
# coupling is gone, cooking recipes concurrently is not sound, and a build that
|
|
# fails intermittently costs far more than a serial one that does not.
|
|
#
|
|
# This does NOT serialise compilation. Each recipe still gets the full -j${JOBS}
|
|
# make budget, so the machine stays busy inside a recipe; only the number of
|
|
# recipes in flight drops to one. Set COOKBOOK_COOK_JOBS>1 to opt back into
|
|
# parallel cooking if you accept the risk.
|
|
COOK_JOBS_DEFAULT=1; [ "$JOBS" -lt "$COOK_JOBS_DEFAULT" ] && COOK_JOBS_DEFAULT="$JOBS"
|
|
export COOKBOOK_COOK_JOBS="${COOKBOOK_COOK_JOBS:-$COOK_JOBS_DEFAULT}"
|
|
APPLY_PATCHES="${APPLY_PATCHES:-1}"
|
|
NO_CACHE=0
|
|
CHECK_SWEEP=0
|
|
ALLOW_UPSTREAM="${REDBEAR_ALLOW_UPSTREAM:-0}"
|
|
ALLOW_DIRTY="${REDBEAR_ALLOW_DIRTY:-0}"
|
|
KEEP_BUILD_STATE="${REDBEAR_KEEP_BUILD_STATE:-0}"
|
|
RELEASE="${REDBEAR_RELEASE:-}"
|
|
|
|
# ARCH and HOST_ARCH are used by the Makefile (mk/config.mk) and the prefix
|
|
# rule (mk/prefix.mk). They are derived from `uname -m` if unset.
|
|
export ARCH="${ARCH:-$(uname -m)}"
|
|
export HOST_ARCH="${HOST_ARCH:-$(uname -m)}"
|
|
|
|
usage() {
|
|
cat <<EOF
|
|
Usage: $(basename "$0") [OPTIONS] [CONFIG]
|
|
|
|
Build a tracked Red Bear OS profile.
|
|
|
|
Options:
|
|
-j, --jobs N Parallel build jobs (default: nproc)
|
|
--upstream Allow Redox/upstream recipe source refresh during build
|
|
--no-cache Force clean rebuild, discarding cached packages
|
|
--release VER Build from the immutable release archive VER
|
|
--allow-dirty Build with uncommitted fork edits (cooks the tree AS-IS)
|
|
--keep-build-state Keep the diagnostic state dir after exit
|
|
--check-sweep Run 'cargo check' on the target's Rust sources (forks +
|
|
config recipes) BEFORE building; abort on any error so all
|
|
compile errors surface at once instead of one per cook
|
|
-h, --help Show this help
|
|
-V, --version Show build-system version/provenance and exit
|
|
|
|
Configs:
|
|
redbear-full Desktop/graphics target (default)
|
|
redbear-mini Text-only console/recovery target
|
|
redbear-grub Text-only with GRUB boot manager
|
|
redbear-bare Stripped-to-bare-minimum: kernel+7-initfs-daemons+zsh login
|
|
|
|
Advanced (rare escape hatches — set as environment variables):
|
|
REDBEAR_ALLOW_CONCURRENT=1 Skip the single-build lock
|
|
REDBEAR_ALLOW_WRONG_BRANCH=1 Build with a fork off its submodule/<name> branch
|
|
REDBEAR_SKIP_PREFIX_REBUILD=1 Do not auto-rebuild a stale prefix sysroot
|
|
REDBEAR_FORCE_TOOLCHAIN_RECOOK=1 Re-cook llvm21/clang21/lld21/rust on a relibc change
|
|
REDBEAR_SKIP_FORK_VERIFY=1 Skip fork version verification (preflight)
|
|
REDBEAR_SKIP_DRIFT_CHECK=1 Skip upstream drift check (preflight)
|
|
REDBEAR_SKIP_FUNCTION_CHECK=1 Skip fork-function verification (preflight)
|
|
REDBEAR_SKIP_COLLISION_CHECK=1 Skip config-vs-package collision detection (preflight)
|
|
REDBEAR_SKIP_PATCH_CONTENT_CHECK=1 Skip orphaned-patch content check (preflight)
|
|
REDBEAR_STRICT_DURABILITY=1 Fail on non-durable source edits (preflight)
|
|
REDBEAR_STRICT_METADATA=1 Fail on recipe metadata issues (preflight)
|
|
REDBEAR_NO_VALIDATE=1 Skip the post-build image validation chain (make)
|
|
REDBEAR_COLLISIONS_WARN=1 Downgrade install-collision failures to warnings (make)
|
|
EOF
|
|
}
|
|
|
|
POSITIONAL=()
|
|
while [ $# -gt 0 ]; do
|
|
case "$1" in
|
|
--upstream) ALLOW_UPSTREAM=1 ;;
|
|
--no-cache) NO_CACHE=1 ;;
|
|
-j|--jobs) shift; JOBS="${1:?--jobs requires a number}" ;;
|
|
--jobs=*) JOBS="${1#*=}" ;;
|
|
--release) shift; RELEASE="${1:?--release requires a version}" ;;
|
|
--release=*) RELEASE="${1#*=}" ;;
|
|
--allow-dirty) ALLOW_DIRTY=1 ;;
|
|
--keep-build-state) KEEP_BUILD_STATE=1 ;;
|
|
--check-sweep) CHECK_SWEEP=1 ;;
|
|
-h|--help)
|
|
usage
|
|
exit 0
|
|
;;
|
|
-V|--version)
|
|
print_version
|
|
exit 0
|
|
;;
|
|
-*)
|
|
echo "Unknown option: $1" >&2
|
|
usage >&2
|
|
exit 1
|
|
;;
|
|
*)
|
|
POSITIONAL+=("$1")
|
|
;;
|
|
esac
|
|
shift
|
|
done
|
|
|
|
if [ ${#POSITIONAL[@]} -gt 1 ]; then
|
|
echo "ERROR: Too many positional arguments" >&2
|
|
usage >&2
|
|
exit 1
|
|
fi
|
|
|
|
[ ${#POSITIONAL[@]} -eq 1 ] && CONFIG="${POSITIONAL[0]}"
|
|
|
|
case "$CONFIG" in
|
|
redbear-full|redbear-mini|redbear-grub|redbear-bare)
|
|
;;
|
|
*)
|
|
echo "ERROR: Unknown config '$CONFIG'" >&2
|
|
echo "Supported: redbear-full, redbear-mini, redbear-grub, redbear-bare" >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
|
|
# Re-export resolved common knobs so this script's gates, build-preflight.sh and
|
|
# the Makefile see the flag-provided values (flags override the deprecated
|
|
# REDBEAR_* env fallbacks). REDBEAR_RELEASE is exported as-is (empty = dev build).
|
|
export REDBEAR_ALLOW_UPSTREAM="$ALLOW_UPSTREAM"
|
|
export REDBEAR_ALLOW_DIRTY="$ALLOW_DIRTY"
|
|
export REDBEAR_KEEP_BUILD_STATE="$KEEP_BUILD_STATE"
|
|
export REDBEAR_RELEASE="$RELEASE"
|
|
|
|
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 "Script version: $BUILD_REDBEAR_VERSION"
|
|
hdr "========================================"
|
|
echo ""
|
|
|
|
cd "$PROJECT_ROOT"
|
|
|
|
# Strict dirty-source gate: refuse to build with uncommitted edits in any
|
|
# Red Bear fork source. Without this, builds cook from WIP trees whose
|
|
# pkgar fingerprints never match the committed HEAD on subsequent builds,
|
|
# producing "works on my machine" failures and stale cache invalidation.
|
|
# The escape hatch REDBEAR_ALLOW_DIRTY=1 is for emergency CI runs where
|
|
# the operator accepts the risk.
|
|
if [ "${REDBEAR_ALLOW_DIRTY:-0}" != "1" ] && [ -z "${REDBEAR_RELEASE:-}" ]; then
|
|
DIRTY_FORKS=()
|
|
for entry in "${REDBEAR_FORK_SOURCES[@]}"; do
|
|
label="${entry%%:*}"
|
|
dir="${entry#*:}"
|
|
[ -d "$dir" ] || continue
|
|
if redbear_is_fork_dirty "$dir"; then
|
|
DIRTY_FORKS+=("$label ($dir)")
|
|
fi
|
|
done
|
|
if [ ${#DIRTY_FORKS[@]} -gt 0 ]; then
|
|
echo "========================================" >&2
|
|
echo " REFUSING TO BUILD WITH UNCOMMITTED EDITS" >&2
|
|
echo "========================================" >&2
|
|
echo "" >&2
|
|
echo "Uncommitted edits detected in:" >&2
|
|
for fork in "${DIRTY_FORKS[@]}"; do
|
|
echo " - $fork" >&2
|
|
done
|
|
echo "" >&2
|
|
echo "Each fork must have a clean HEAD so pkgar fingerprints reflect" >&2
|
|
echo "committed state. Commit, stash, or reset your changes before" >&2
|
|
echo "running a canonical build." >&2
|
|
echo "" >&2
|
|
echo "Override (emergency only): $0 --allow-dirty $*" >&2
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
# Per AGENTS.md § "BRANCH AND SUBMODULE POLICY (ABSOLUTE)": each Red Bear
|
|
# fork worktree must be on the canonical submodule/<name> branch.
|
|
if [ "${REDBEAR_ALLOW_WRONG_BRANCH:-0}" != "1" ] && [ -z "${REDBEAR_RELEASE:-}" ]; then
|
|
redbear_check_fork_branches
|
|
fi
|
|
|
|
if [ -x "$PROJECT_ROOT/local/scripts/verify-overlay-integrity.sh" ] && [ -z "${REDBEAR_RELEASE:-}" ]; then
|
|
echo "${C_INFO}>>>${C_RESET} Verifying overlay integrity..."
|
|
if ! "$PROJECT_ROOT/local/scripts/verify-overlay-integrity.sh" --quiet; then
|
|
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 "${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 "${C_INFO}>>>${C_RESET} Overlay integrity OK."
|
|
fi
|
|
echo ""
|
|
fi
|
|
|
|
# Per AGENTS.md: local recipes ALWAYS supersede WIP.
|
|
# 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 "${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
|
|
while IFS= read -r -d '' wip_dir; do
|
|
if [ ! -L "$wip_dir" ]; then
|
|
wip_rel=$(realpath --relative-to="$(dirname "$wip_dir")" "$local_recipe")
|
|
rm -rf "$wip_dir"
|
|
ln -sf "$wip_rel" "$wip_dir"
|
|
echo " WIP $pkg -> local ($wip_rel)"
|
|
fi
|
|
done < <(find "$PROJECT_ROOT"/recipes/wip -maxdepth 5 -name "$pkg" -type d -print0 2>/dev/null || true)
|
|
done
|
|
echo ""
|
|
fi
|
|
|
|
# The build system no longer stashes your working tree (it cooks committed HEAD,
|
|
# or your tree AS-IS under REDBEAR_ALLOW_DIRTY=1). A prior version stashed dirty
|
|
# forks and, due to a git-output-parsing bug, sometimes failed to restore them,
|
|
# leaving "redbear-build-*" stashes behind. Surface any leftovers read-only so
|
|
# stranded WIP is never silently forgotten. This NEVER touches your tree.
|
|
_rb_leftover_total=0
|
|
for _entry in "${REDBEAR_FORK_SOURCES[@]}"; do
|
|
_d="${_entry#*:}"
|
|
[ -d "$_d/.git" ] || continue
|
|
_n=$(git -C "$_d" stash list 2>/dev/null | grep -c "redbear-build-" || true)
|
|
if [ "${_n:-0}" -gt 0 ]; then
|
|
echo "${C_WARN}>>> NOTE:${C_RESET} ${_entry%%:*}: $_n leftover 'redbear-build-*' stash(es) from an older build — recover: git -C '$_d' stash list" >&2
|
|
_rb_leftover_total=$((_rb_leftover_total + _n))
|
|
fi
|
|
done
|
|
[ "$_rb_leftover_total" -gt 0 ] && echo "${C_WARN}>>> NOTE:${C_RESET} $_rb_leftover_total stranded build-stash(es) total; see local/recovered-stashes/README.md" >&2
|
|
|
|
# EXIT trap: always dump diagnostics and preserve the original exit code. The trap fires
|
|
# even on signals (SIGINT/SIGTERM/SIGHUP) because we don't override
|
|
# ERR/DEBUG and we use EXIT (the POSIX-standard pseudo-signal that fires
|
|
# for any exit path). The trap runs LAST in the shell's exit order, so
|
|
# any explicit `exit N` we do inside the script triggers it with $? = N.
|
|
redbear_exit_trap() {
|
|
local rc=$?
|
|
# Phase 2: dump failure diagnostics if the build failed.
|
|
if [ "$rc" -ne 0 ]; then
|
|
redbear_dump_failure_diagnostics "$rc"
|
|
fi
|
|
# Phase 3: clean up the state dir unless the user wants to inspect it.
|
|
if [ -d "$REDBEAR_BUILD_STATE_DIR" ] && [ "${REDBEAR_KEEP_BUILD_STATE:-0}" != "1" ]; then
|
|
rm -rf "$REDBEAR_BUILD_STATE_DIR"
|
|
fi
|
|
exit "$rc"
|
|
}
|
|
trap redbear_exit_trap EXIT
|
|
|
|
# Failure diagnostics: collect the last N lines of every cookbook log in
|
|
# $REDBEAR_BUILD_LOGS_DIR plus any mktemp leftovers from the build, list
|
|
# partial recipe build states, report orphaned cook processes, and point
|
|
# the operator at the state directory for post-mortem analysis. Designed to
|
|
# give the operator a complete picture without requiring them to manually
|
|
# grep through /tmp.
|
|
redbear_dump_failure_diagnostics() {
|
|
local rc="$1"
|
|
echo ""
|
|
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:"
|
|
echo " $REDBEAR_BUILD_STATE_DIR"
|
|
echo ""
|
|
echo "Pass --keep-build-state to retain this directory"
|
|
echo "after the script exits."
|
|
echo ""
|
|
|
|
# Last 30 lines of each cookbook log we captured.
|
|
if [ -d "$REDBEAR_BUILD_LOGS_DIR" ] && [ -n "$(ls -A "$REDBEAR_BUILD_LOGS_DIR" 2>/dev/null)" ]; then
|
|
echo "--- Last 30 lines of each captured build log ---"
|
|
local log
|
|
for log in "$REDBEAR_BUILD_LOGS_DIR"/*.log; do
|
|
[ -f "$log" ] || continue
|
|
echo ""
|
|
echo "==> $(basename "$log")"
|
|
tail -30 "$log"
|
|
done
|
|
fi
|
|
|
|
# Cookbook / cargo processes that may be orphaned from a signal kill.
|
|
# These are often the actual cause of subsequent build failures (held
|
|
# locks, half-written files).
|
|
echo ""
|
|
echo "--- Orphaned cook/cargo processes (if any) ---"
|
|
local orphan
|
|
orphan=$(ps -eo pid,ppid,etime,comm 2>/dev/null | \
|
|
grep -E 'repo|cargo|rustc|cc1|cookbook_redbear_redoxer' | \
|
|
grep -v grep || true)
|
|
if [ -n "$orphan" ]; then
|
|
echo "$orphan"
|
|
else
|
|
echo " (none)"
|
|
fi
|
|
|
|
# Recipe build states: which packages have a stage.pkgar (success)
|
|
# vs which have only stage.tmp (in-progress).
|
|
echo ""
|
|
echo "--- Per-recipe build state ---"
|
|
# Scope to THIS build only. The dump walks the whole recipe tree, so without
|
|
# a time filter a bare/mini build would list graphical packages (mesa, qt,
|
|
# kf6-*, sddm, ...) whose stage/stage.tmp are leftovers from an earlier
|
|
# redbear-full build — packages not even part of the target being built.
|
|
# Report an artifact only if it was modified during this build.
|
|
local recipe_dir target_arch
|
|
local _rb_start="${REDBEAR_BUILD_START_EPOCH:-0}"
|
|
local _rb_shown=0
|
|
for recipe_dir in "$PROJECT_ROOT"/recipes/*/ "$PROJECT_ROOT"/local/recipes/*/*/; do
|
|
[ -d "$recipe_dir" ] || continue
|
|
[ -f "$recipe_dir/recipe.toml" ] || continue
|
|
local pkg
|
|
pkg=$(basename "$recipe_dir")
|
|
for target_arch in "$PROJECT_ROOT"/recipes/"$pkg"/target/*/ \
|
|
"$PROJECT_ROOT"/local/recipes/*/"$pkg"/target/*/; do
|
|
[ -d "$target_arch" ] || continue
|
|
local stage stage_tmp size ts
|
|
stage="$target_arch/stage"
|
|
stage_tmp="$target_arch/stage.tmp"
|
|
if [ -d "$stage_tmp" ]; then
|
|
ts=$(stat -c %Y "$stage_tmp" 2>/dev/null || echo 0)
|
|
[ "$ts" -ge "$_rb_start" ] || continue
|
|
size=$(du -sh "$stage_tmp" 2>/dev/null | cut -f1)
|
|
echo " $pkg [$target_arch]: INCOMPLETE (stage.tmp: $size)"
|
|
_rb_shown=1
|
|
elif [ -d "$stage" ]; then
|
|
ts=$(stat -c %Y "$stage" 2>/dev/null || echo 0)
|
|
[ "$ts" -ge "$_rb_start" ] || continue
|
|
echo " $pkg [$target_arch]: complete (this build)"
|
|
_rb_shown=1
|
|
fi
|
|
done
|
|
done
|
|
[ "$_rb_shown" = "0" ] && echo " (no recipe artifacts were modified during this build)"
|
|
|
|
echo ""
|
|
echo "========================================"
|
|
echo ""
|
|
}
|
|
|
|
if [ "$APPLY_PATCHES" = "1" ] && [ -z "${REDBEAR_RELEASE:-}" ]; then
|
|
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 "${C_INFO}>>>${C_RESET} Release mode: fork sources include all patches; no separate patch step needed."
|
|
fi
|
|
|
|
if [ ! -f "target/release/repo" ]; then
|
|
echo "${C_INFO}>>>${C_RESET} Building cookbook binary (missing)..."
|
|
cargo build --release
|
|
redbear_record_cookbook_fingerprint
|
|
else
|
|
# Rebuild cookbook if any .rs file in src/ changed since the last
|
|
# successful build. The previous existence-only check let stale
|
|
# binaries survive `git pull`, causing CLI flags to silently no-op.
|
|
CURRENT_COOKBOOK_HASH=$(redbear_compute_cookbook_fingerprint)
|
|
STORED_COOKBOOK_HASH=""
|
|
[ -f "target/release/.cookbook-src-fingerprint" ] && \
|
|
STORED_COOKBOOK_HASH=$(cat "target/release/.cookbook-src-fingerprint")
|
|
if [ "$CURRENT_COOKBOOK_HASH" != "$STORED_COOKBOOK_HASH" ]; then
|
|
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 "${C_INFO}>>>${C_RESET} Cookbook binary is up to date (${CURRENT_COOKBOOK_HASH:0:12})"
|
|
fi
|
|
fi
|
|
|
|
if [ "$CONFIG" = "redbear-full" ]; then
|
|
redbear_ensure_relibc_desktop_surface
|
|
fi
|
|
|
|
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 "${C_INFO}>>>${C_RESET} Found $FW_COUNT AMD firmware blobs"
|
|
else
|
|
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
|
|
|
|
# Pre-build cargo check sweep (opt-in via --check-sweep). Runs before any cook
|
|
# so all Rust compile errors surface at once; a failure aborts before the
|
|
# expensive prefix/cook cycle.
|
|
if [ "$CHECK_SWEEP" = "1" ]; then
|
|
redbear_check_sweep || exit 1
|
|
fi
|
|
|
|
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
|
|
# build/sysroot dirs across all recipes. Low-level packages (relibc,
|
|
# kernel, base) provide the C runtime and compiler support libs; when
|
|
# they change, autotools packages (pcre2, gettext, libiconv, etc.)
|
|
# retain stale configure/libtool scripts that reference the old runtime,
|
|
# causing "libtool version mismatch" and "not a valid libtool object"
|
|
# errors. Cleaning build/ and sysroot/ forces re-configuration while
|
|
# preserving stage/ and source/ so the cookbook can skip unchanged
|
|
# packages that don't use autotools.
|
|
if [ "$NO_CACHE" != "1" ]; then
|
|
STALE_DETECTED=0
|
|
RELIBC_INVALIDATED=0
|
|
# Whether a fork that userspace binaries actually LINK changed. Only relibc
|
|
# (the C runtime) and base (compiler-support/startup libs) are linked into
|
|
# userspace; kernel/bootloader/installer are standalone and a change to them
|
|
# must NOT force a full userspace re-cook (that wasted ~1h on kernel-only
|
|
# fixes). Gates the build/sysroot clean below.
|
|
USERSPACE_RUNTIME_STALE=0
|
|
for src in relibc kernel base bootloader installer; do
|
|
src_dir="$PROJECT_ROOT/local/sources/$src"
|
|
pkgar="$PROJECT_ROOT/repo/x86_64-unknown-redox/$src.pkgar"
|
|
fingerprint="$PROJECT_ROOT/repo/x86_64-unknown-redox/$src.source-fingerprint"
|
|
if [ -d "$src_dir/.git" ] && [ -f "$pkgar" ]; then
|
|
src_commit=$(git -C "$src_dir" rev-parse HEAD 2>/dev/null || echo "")
|
|
last_commit=$(cat "$fingerprint" 2>/dev/null || echo "")
|
|
|
|
# Working-tree dirtiness: tracked modifications, staged
|
|
# changes, and untracked files all count. Without this,
|
|
# uncommitted edits (e.g. an in-progress debug session)
|
|
# silently bypass the stale-detect and the next build
|
|
# cooks from a fingerprint that claims "up to date" but
|
|
# is actually older than the working tree.
|
|
src_dirty=0
|
|
if ! git -C "$src_dir" diff --quiet HEAD 2>/dev/null \
|
|
|| ! git -C "$src_dir" diff --cached --quiet HEAD 2>/dev/null \
|
|
|| [ -n "$(git -C "$src_dir" ls-files --others --exclude-standard 2>/dev/null)" ]; then
|
|
src_dirty=1
|
|
fi
|
|
|
|
if [ -n "$src_commit" ] && { [ "$src_commit" != "$last_commit" ] || [ "$src_dirty" = "1" ]; }; then
|
|
if [ "$src_dirty" = "1" ] && [ "$src_commit" = "$last_commit" ]; then
|
|
echo "${C_INFO}>>>${C_RESET} Stale $src detected (working tree has uncommitted changes); invalidating..."
|
|
else
|
|
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
|
|
STALE_DETECTED=1
|
|
[ "$src" = "relibc" ] && RELIBC_INVALIDATED=1
|
|
# NB: base ships NO libraries (daemons/drivers/init) and relibc is
|
|
# a shared libc.so.6 — neither forces a userspace-wide relink. Only
|
|
# the static initfs-critical binaries are handled, selectively, below.
|
|
fi
|
|
fi
|
|
done
|
|
|
|
# Shared forks not covered by the pkgar loop above. userutils ships a pkgar
|
|
# (login/getty/etc); syscall/libredox/redox-scheme are Cargo path-dep crates
|
|
# with NO pkgar of their own but are linked into most of userspace, so
|
|
# cookbook's per-recipe cache cannot detect a change in them. Detect a source
|
|
# change and force a full userspace relink to prevent stale-binary skew.
|
|
for src in userutils syscall libredox redox-scheme; do
|
|
src_dir="$PROJECT_ROOT/local/sources/$src"
|
|
fingerprint="$PROJECT_ROOT/repo/x86_64-unknown-redox/$src.source-fingerprint"
|
|
[ -d "$src_dir/.git" ] || continue
|
|
src_commit=$(git -C "$src_dir" rev-parse HEAD 2>/dev/null || echo "")
|
|
last_commit=$(cat "$fingerprint" 2>/dev/null || echo "")
|
|
src_dirty=0
|
|
if ! git -C "$src_dir" diff --quiet HEAD 2>/dev/null \
|
|
|| ! git -C "$src_dir" diff --cached --quiet HEAD 2>/dev/null \
|
|
|| [ -n "$(git -C "$src_dir" ls-files --others --exclude-standard 2>/dev/null)" ]; then
|
|
src_dirty=1
|
|
fi
|
|
# 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 "${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".*
|
|
fi
|
|
done
|
|
|
|
|
|
# Vendored-recipe source staleness (the Qt/KDE 6.11.0->6.11.1 / 6.10->6.28
|
|
# class). Many recipes under local/recipes vendor their upstream tree in git
|
|
# at <recipe>/source/ AND declare a [source] tar=. Cookbook keys its build
|
|
# cache on the source.tar hash (source_identifier), but the ACTUAL build
|
|
# input is the vendored source/ tree. When a version bump propagates into
|
|
# source/ WITHOUT changing source.tar (e.g. source.tar was already the new
|
|
# version, or a manual source/ edit), cookbook sees an unchanged
|
|
# source_identifier and reuses a STALE stage — the desktop then builds the
|
|
# old version (qtshadertools wanted Qt 6.11.1 but got a cached 6.11.0 qtbase).
|
|
#
|
|
# Fix: fingerprint each vendored source/ by its git tree hash (content-based,
|
|
# mtime-independent) plus a working-tree-dirty flag, and rm that recipe's
|
|
# target/ when it changes. First observation for a recipe only SEEDS the
|
|
# fingerprint (no invalidation) so this never triggers a spurious full
|
|
# rebuild; thereafter any source/ change forces exactly that recipe to
|
|
# re-cook. Mirrors verify-external-source-versions.sh at the cache layer.
|
|
while IFS= read -r _rt; do
|
|
_rdir="$(dirname "$_rt")"
|
|
_sdir="$_rdir/source"
|
|
[ -d "$_sdir" ] || continue
|
|
_tgt="$_rdir/target/x86_64-unknown-redox"
|
|
[ -f "$_tgt/stage.pkgar" ] || continue # nothing cooked yet -> nothing stale
|
|
_relsrc="${_sdir#"$PROJECT_ROOT"/}"
|
|
_tree="$(git -C "$PROJECT_ROOT" rev-parse "HEAD:$_relsrc" 2>/dev/null || echo "")"
|
|
[ -n "$_tree" ] || continue # source/ not git-tracked -> skip
|
|
_dirty=""
|
|
git -C "$PROJECT_ROOT" diff --quiet HEAD -- "$_relsrc" 2>/dev/null || _dirty="-dirty"
|
|
[ -n "$(git -C "$PROJECT_ROOT" ls-files --others --exclude-standard -- "$_relsrc" 2>/dev/null)" ] && _dirty="-dirty"
|
|
_cur="${_tree}${_dirty}"
|
|
_fp="$_tgt/.redbear-source-tree"
|
|
_last="$(cat "$_fp" 2>/dev/null || echo "")"
|
|
if [ -z "$_last" ]; then
|
|
printf '%s\n' "$_cur" > "$_fp" # seed only, trust the existing build
|
|
elif [ "$_cur" != "$_last" ]; then
|
|
echo "${C_INFO}>>>${C_RESET} Stale vendored source: $(basename "$_rdir") (source/ changed since last cook) — invalidating target/"
|
|
rm -rf "$_rdir/target"
|
|
STALE_DETECTED=1
|
|
fi
|
|
done < <(find "$PROJECT_ROOT/local/recipes" -name recipe.toml -not -path '*/wip/*' 2>/dev/null)
|
|
|
|
# (The blanket "wipe every recipe's build/sysroot when a runtime fork changed"
|
|
# step used to live here. It was the bug: it recompiled the entire dynamically
|
|
# -linked desktop — Qt, KF6, mesa, sddm — on any relibc/base/syscall change,
|
|
# which is exactly what a shared-libc system never needs. Replaced by the
|
|
# selective, static-consumer-only invalidation below.)
|
|
|
|
# Downstream ABI-staleness prevention (relibc consumers).
|
|
#
|
|
# The loop above rebuilds relibc/kernel/base when THEIR OWN sources
|
|
# change, but statically-linked consumers keep their pkgars as long as
|
|
# THEIR sources are unchanged. relibc is both the C runtime AND the
|
|
# Redox scheme/syscall protocol shim, so a consumer built against an
|
|
# older relibc bakes in the OLD protocol. For boot-critical binaries
|
|
# copied into the initfs from the sysroot by
|
|
# recipes/core/base-initfs/recipe.toml this skew is FATAL, not cosmetic:
|
|
# - a stale getty issued the pre-"userspace fd allocation" two-step
|
|
# scheme-open and got EACCES opening /scheme/rand (login dead);
|
|
# - a stale redoxfs panicked "failed to create pipe".
|
|
# Both shipped in an ISO whose freshly-built daemons (randd/ptyd/...)
|
|
# already spoke the new protocol — a silent, hard-to-trace mismatch.
|
|
#
|
|
# Rule: invalidate an ABI-critical consumer when it would link an older
|
|
# relibc than the one this build will ship. That happens either because
|
|
# relibc is about to rebuild (RELIBC_INVALIDATED=1, or its pkgar is
|
|
# already gone) or because a prior run left relibc.pkgar newer than the
|
|
# consumer's pkgar (the exact getty/redoxfs case: relibc rebuilt hours
|
|
# after userutils/redoxfs).
|
|
#
|
|
# NB: if you add a binary to the base-initfs sysroot copy-set, add its
|
|
# recipe name to ABI_CRITICAL_PKGS below.
|
|
# relibc is statically linked into EVERY userspace binary, so a relibc
|
|
# change invalidates every consumer — not just the initfs-critical few. A
|
|
# stale consumer silently bakes in OLD relibc behaviour (old scheme/syscall
|
|
# protocol, missing syscall fixes, the tokio/epoll retry, ...), and
|
|
# diagnosing bugs against such stale binaries wastes enormous time. The rule
|
|
# is therefore bulletproof, not selective:
|
|
#
|
|
# * If relibc is (re)building this run, OR its pkgar is missing, invalidate
|
|
# ALL package pkgars + recipe build trees so every binary relinks the
|
|
# freshly-built relibc.
|
|
# * Otherwise (relibc unchanged this run), still catch the interrupted-
|
|
# build case: any consumer pkgar OLDER than relibc.pkgar was left behind
|
|
# by a prior run that rebuilt relibc but died before relinking it — that
|
|
# exact skew (relibc fixed, daemons stale) shipped a broken login image.
|
|
#
|
|
# source/ trees live under the recipe dir (not target/), so re-cook is safe.
|
|
REPO_DIR="$PROJECT_ROOT/repo/x86_64-unknown-redox"
|
|
RELIBC_PKGAR="$REPO_DIR/relibc.pkgar"
|
|
relibc_rebuilding=0
|
|
if [ "$RELIBC_INVALIDATED" = "1" ] || [ ! -f "$RELIBC_PKGAR" ]; then
|
|
relibc_rebuilding=1
|
|
fi
|
|
# Heavy, ABI-inert toolchain packages preserved across relibc invalidation.
|
|
#
|
|
# These are BUILD-TIME-ONLY static archives (built with BUILD_SHARED_LIBS=off;
|
|
# they ship no runtime .so of their own). Their references to libc/relibc
|
|
# symbols are UNRESOLVED in the .a and get resolved at the CONSUMER's final
|
|
# link — e.g. mesa links libLLVM*.a into libgallium and resolves malloc/etc.
|
|
# against the freshly-built relibc at THAT point. So a relibc protocol change
|
|
# is absorbed by relinking the consumer (mesa is NOT on this list and stays
|
|
# invalidated), and recompiling all of LLVM/Rust from scratch to pick up a
|
|
# relibc change is unnecessary — it just burns 1-2h per base/relibc bump.
|
|
#
|
|
# This trades a rare theoretical risk (a relibc change to a libc struct/inline
|
|
# actually baked into an llvm .o) for a large, repeated speedup. The churn
|
|
# during driver/desktop work is Redox scheme/syscall-protocol shims, which
|
|
# these compilers never call, so the default is safe. Escape hatch:
|
|
# REDBEAR_FORCE_TOOLCHAIN_RECOOK=1 restores the full from-scratch nuke.
|
|
TOOLCHAIN_PRESERVE="llvm21 clang21 lld21 rust"
|
|
_rb_preserve_toolchain() {
|
|
[ "${REDBEAR_FORCE_TOOLCHAIN_RECOOK:-0}" = "1" ] && return 1
|
|
case " $TOOLCHAIN_PRESERVE " in
|
|
*" $1 "*) return 0 ;;
|
|
*) return 1 ;;
|
|
esac
|
|
}
|
|
# ── Selective, static-consumer-only ABI invalidation ───────────────
|
|
# relibc is a SHARED library (libc.so.6): dynamically-linked userspace — Qt,
|
|
# KF6, mesa, sddm, the whole desktop — resolves it at RUNTIME, so a relibc
|
|
# rebuild needs NO recompile there. That is exactly why upgrading glibc on
|
|
# Linux does not recompile the system. `base` ships no libraries at all
|
|
# (daemons/drivers/init). The ONLY binaries that can genuinely go stale are
|
|
# the STATIC, boot-critical initfs binaries that bake in the Redox
|
|
# scheme/syscall protocol before libc.so is available — the getty / redoxfs /
|
|
# init / randd class. Those come from a small fixed set of forks; when the
|
|
# boot ABI changes (relibc rebuilds, or a syscall/libredox/redox-scheme
|
|
# protocol crate changed -> USERSPACE_RUNTIME_STALE), relink ONLY them.
|
|
#
|
|
# This restores the selective rule the old "nuke every pkgar + wipe every
|
|
# build/sysroot" version had replaced (which recompiled the entire desktop on
|
|
# any driver/acpi/base edit). For a genuine relibc *C-ABI* break — a libc
|
|
# struct/inline baked into a compiled consumer, which is rare — force a full
|
|
# clean rebuild with `--no-cache`.
|
|
# base-initfs builds the STATIC boot-critical binaries baked into the initfs
|
|
# (init, logd, ramfs, randd, zerod, acpid, pcid, vesad, fbcond, …). It is a
|
|
# `custom` recipe whose content-hash cache does NOT track the boot-ABI crates
|
|
# (relibc/libredox/syscall/redox-scheme), so a boot-ABI change leaves it
|
|
# "cached" with stale binaries — pid1 `init` then dies at boot with a bogus
|
|
# "memory allocation failed" (ABI skew), never reaching login. It must relink
|
|
# alongside base/redoxfs/userutils whenever the boot ABI changes.
|
|
ABI_CRITICAL_PKGS="base base-initfs redoxfs userutils bootstrap"
|
|
_boot_abi_changed=0
|
|
[ "$relibc_rebuilding" = "1" ] && _boot_abi_changed=1
|
|
[ "$USERSPACE_RUNTIME_STALE" = "1" ] && _boot_abi_changed=1
|
|
if [ "$_boot_abi_changed" = "1" ]; then
|
|
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" \
|
|
-path "*/$_pkg/target" -type d -exec rm -rf {} + 2>/dev/null || true
|
|
done
|
|
fi
|
|
fi
|
|
|
|
# Host fstools staleness (redox_installer / redoxfs FUSE used by `make live`).
|
|
#
|
|
# `make live` assembles the image with the HOST-compiled redox_installer from
|
|
# build/fstools/bin, built once by the `fstools` make target via
|
|
# `cargo install --path local/sources/installer`. That target is a DIRECTORY
|
|
# target (build/fstools) with NO prerequisite on the installer/redoxfs sources,
|
|
# so once build/fstools exists make never re-runs the cargo install — a source
|
|
# change to either fork silently never reaches image assembly. This bit us: an
|
|
# installer change to config-override handling appeared to "do nothing" because
|
|
# make live kept running a 6-day-old installer binary. Mirror the recipe
|
|
# staleness logic: if either fork's committed HEAD or working tree is newer
|
|
# than build/fstools, remove it so make rebuilds the host tools. Runs
|
|
# unconditionally (the NO_CACHE repo clean above does not touch build/fstools).
|
|
FSTOOLS_DIR="$PROJECT_ROOT/build/fstools"
|
|
if [ -d "$FSTOOLS_DIR" ]; then
|
|
fstools_ts=$(stat -c %Y "$FSTOOLS_DIR" 2>/dev/null || echo "0")
|
|
fstools_stale=0
|
|
for src in installer redoxfs; do
|
|
src_dir="$PROJECT_ROOT/local/sources/$src"
|
|
[ -d "$src_dir/.git" ] || continue
|
|
src_ts=$(git -C "$src_dir" log -1 --format=%ct HEAD 2>/dev/null || echo "0")
|
|
src_dirty=0
|
|
if ! git -C "$src_dir" diff --quiet HEAD 2>/dev/null \
|
|
|| ! git -C "$src_dir" diff --cached --quiet HEAD 2>/dev/null \
|
|
|| [ -n "$(git -C "$src_dir" ls-files --others --exclude-standard 2>/dev/null)" ]; then
|
|
src_dirty=1
|
|
fi
|
|
if { [ "$src_ts" != "0" ] && [ "$fstools_ts" != "0" ] && [ "$src_ts" -gt "$fstools_ts" ]; } \
|
|
|| [ "$src_dirty" = "1" ]; then
|
|
echo "${C_INFO}>>>${C_RESET} Stale host fstools detected ($src newer than build/fstools); rebuilding host installer/redoxfs..."
|
|
fstools_stale=1
|
|
fi
|
|
done
|
|
if [ "$fstools_stale" = "1" ]; then
|
|
rm -rf "$FSTOOLS_DIR"
|
|
fi
|
|
fi
|
|
|
|
PREFIX_LIBC="$PROJECT_ROOT/prefix/x86_64-unknown-redox/sysroot/x86_64-unknown-redox/lib/libc.a"
|
|
STALE_PREFIX=0
|
|
STALE_SRCS=()
|
|
if [ -f "$PREFIX_LIBC" ]; then
|
|
for src in relibc kernel base; do
|
|
src_dir="$PROJECT_ROOT/local/sources/$src"
|
|
if [ -d "$src_dir/.git" ]; then
|
|
fork_ts=$(git -C "$src_dir" log -1 --format=%ct HEAD 2>/dev/null || echo "0")
|
|
prefix_ts=$(stat -c %Y "$PREFIX_LIBC" 2>/dev/null || echo "0")
|
|
# Also check for uncommitted changes in the working tree
|
|
src_dirty=0
|
|
if ! git -C "$src_dir" diff --quiet HEAD 2>/dev/null \
|
|
|| ! git -C "$src_dir" diff --cached --quiet HEAD 2>/dev/null \
|
|
|| [ -n "$(git -C "$src_dir" ls-files --others --exclude-standard 2>/dev/null)" ]; then
|
|
src_dirty=1
|
|
fi
|
|
if [ "$fork_ts" != "0" ] && [ "$prefix_ts" != "0" ] && { [ "$fork_ts" -gt "$prefix_ts" ] || [ "$src_dirty" = "1" ]; }; 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 "${C_INFO}>>>${C_RESET} Stale $src detected (working tree has uncommitted changes); prefix will be rebuilt."
|
|
else
|
|
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
|
|
fi
|
|
fi
|
|
done
|
|
fi
|
|
|
|
# Auto-rebuild prefix when stale (replaces previous warning-only behavior).
|
|
# Per Phase 1.3 of local/docs/SYSTEM-STABILITY-AND-UPSTREAM-SYNC-PLAN.md, this
|
|
# actually invokes `make prefix` so the toolchain stays in sync with the
|
|
# 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
|
|
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
|
|
STALE_PREFIX=0
|
|
fi
|
|
|
|
# ── Toolchain guard: this fork is GCC 16 only ────────────────────────────────
|
|
# `make prefix` seeds gcc-install from upstream's static.redox-os.org tarball,
|
|
# which is GCC 13.2.0, and no make rule replaces it — the GCC 16 compiler is
|
|
# installed by local/scripts/install-gcc16-toolchain.sh. A GCC 13 toolchain
|
|
# compiles most of the tree and only fails deep into kwin, on the missing
|
|
# C++23 std::ranges::to, so it is far cheaper to refuse it here.
|
|
#
|
|
# All three locations are checked because the redoxer one has the highest
|
|
# priority (src/cook/script.rs prepends it to PATH last) and a mismatch
|
|
# between them is exactly how a "GCC 16" build silently uses GCC 13.
|
|
_rb_tc_target="${TARGET:-x86_64-unknown-redox}"
|
|
_rb_tc_bad=0
|
|
for _rb_tc_dir in \
|
|
"$PROJECT_ROOT/prefix/$_rb_tc_target/gcc-install" \
|
|
"$PROJECT_ROOT/prefix/$_rb_tc_target/sysroot" \
|
|
"$HOME/.redoxer/$_rb_tc_target/toolchain"; do
|
|
_rb_tc_cc="$_rb_tc_dir/bin/$_rb_tc_target-gcc"
|
|
[ -x "$_rb_tc_cc" ] || continue
|
|
_rb_tc_ver="$("$_rb_tc_cc" -dumpversion 2>/dev/null || echo unknown)"
|
|
case "$_rb_tc_ver" in
|
|
16.*) ;;
|
|
*)
|
|
echo "${C_ERR}>>> ERROR:${C_RESET} $_rb_tc_dir reports GCC $_rb_tc_ver; Red Bear requires GCC 16." >&2
|
|
_rb_tc_bad=1
|
|
;;
|
|
esac
|
|
done
|
|
if [ "$_rb_tc_bad" = "1" ]; then
|
|
echo "${C_ERR}>>>${C_RESET} Build and install the GCC 16 cross toolchain, then re-run:" >&2
|
|
echo " local/scripts/build-gcc16-cross.sh" >&2
|
|
echo " local/scripts/install-gcc16-toolchain.sh" >&2
|
|
echo " See local/docs/TOOLCHAIN-GCC16.md." >&2
|
|
exit 1
|
|
fi
|
|
unset _rb_tc_target _rb_tc_bad _rb_tc_dir _rb_tc_cc _rb_tc_ver
|
|
|
|
if [ "$NO_CACHE" = "1" ]; then
|
|
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
|
|
find "$PROJECT_ROOT"/recipes -maxdepth 3 -name "target" -type d -exec rm -rf {} + 2>/dev/null || true
|
|
fi
|
|
|
|
if [ -n "${REDBEAR_RELEASE:-}" ]; then
|
|
bash "$PROJECT_ROOT/local/scripts/build-release-mode.sh" --release="$REDBEAR_RELEASE" --config="$CONFIG" --extra-package=relibc
|
|
fi
|
|
|
|
bash "$PROJECT_ROOT/local/scripts/build-preflight.sh" --config="$CONFIG" ${REDBEAR_RELEASE:+--release="$REDBEAR_RELEASE"} --extra-package=relibc
|
|
|
|
bash "$PROJECT_ROOT/local/scripts/sync-versions.sh" --check || {
|
|
echo "WARNING: In-house crate version drift detected. Run './local/scripts/sync-versions.sh' to fix."
|
|
echo " Continuing build — versions will be corrected on next source fetch."
|
|
}
|
|
|
|
# Pre-cook critical packages that may fail in the dependency chain.
|
|
# --with-package-deps resolves ALL transitive deps; pre-cooking ensures
|
|
# the repo has valid pkgars before make live processes the full graph.
|
|
# Only pre-cook the desktop chain for redbear-full; mini/grub don't need it.
|
|
# llvm21 is a Mesa (graphics) dep — only needed when the Mesa chain is in scope.
|
|
# --- Host redoxer toolchain provisioning ------------------------------------
|
|
# Cooking a HOST tool (e.g. host:xz, pulled in to extract .tar.xz sources for the
|
|
# libclc -> clang21 host-build path) makes cookbook_redbear_redoxer look for a
|
|
# host toolchain at ~/.redoxer/<host-target>/toolchain. Unlike the redox-target
|
|
# toolchain, host toolchains are NOT published on static.redox-os.org, so
|
|
# redoxer's fallback download 404s ("redoxer env: unable to init toolchain") and
|
|
# every host cook fails — silently, since pre-cook failures are non-fatal, so it
|
|
# surfaces later as the whole desktop stack going missing.
|
|
#
|
|
# The redox-target toolchain already on disk is a full host+redox bundle (its
|
|
# clang defaults to x86_64-unknown-linux-gnu and it ships rust-std for the host),
|
|
# so it is a valid host toolchain. Provision the host toolchain as a symlink to
|
|
# it. A symlink (rather than exporting REDOXER_TOOLCHAIN) avoids the documented
|
|
# REDOXER_TOOLCHAIN Rust-version pitfall in PACKAGE-BUILD-QUIRKS.md. Idempotent.
|
|
_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 "${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
|
|
|
|
# --- Ensure the redoxer toolchain LLVM carries the AMDGPU (+NVPTX) backend ---
|
|
# Mesa resolves `dependency('llvm', modules: [amdgpu, ...])` against the host
|
|
# llvm-config in the redoxer toolchain (COOKBOOK_HOST_SYSROOT, set by the mesa
|
|
# recipe). The prebuilt toolchain LLVM is compiled X86;AArch64;RISCV only — no
|
|
# GPU codegen backend — so radeonsi/radv AND the Intel iris/ANV CLC precompile
|
|
# path fail that dependency and the whole desktop (Mesa) stack silently drops.
|
|
# The prebuilt host toolchain LLVM is not regenerated by the (binary) prefix, so
|
|
# cook host:llvm21 (its recipe carries X86;AMDGPU;NVPTX) and splice its
|
|
# llvm-config + libLLVM.so + AMDGPU headers/static libs into the toolchain.
|
|
# Idempotent: a fast no-op once the toolchain already reports AMDGPU.
|
|
if [ "$CONFIG" = "redbear-full" ]; then
|
|
_TC="$HOME/.redoxer/x86_64-unknown-redox/toolchain"
|
|
_REPO="$PROJECT_ROOT/target/release/repo"
|
|
if [ -x "$_TC/bin/llvm-config" ] \
|
|
&& ! "$_TC/bin/llvm-config" --targets-built 2>/dev/null | grep -qw AMDGPU; then
|
|
echo "${C_INFO}>>>${C_RESET} Toolchain LLVM lacks the AMDGPU backend — cooking host:llvm21 (X86;AMDGPU;NVPTX) and splicing it into the toolchain (Mesa needs this)..."
|
|
if REDBEAR_CANONICAL_BUILD=1 "$_REPO" cook host:llvm21; then
|
|
_ST="$PROJECT_ROOT/recipes/dev/llvm21/target/x86_64-unknown-linux-gnu"
|
|
_LC="$_ST/stage.runtime/usr/bin/llvm-config"
|
|
if [ -x "$_LC" ] && "$_LC" --targets-built | grep -qw AMDGPU; then
|
|
if [ ! -d "$_TC/.pre-amdgpu-backup" ]; then
|
|
mkdir -p "$_TC/.pre-amdgpu-backup/bin" "$_TC/.pre-amdgpu-backup/lib"
|
|
cp -a "$_TC/bin/llvm-config" "$_TC/.pre-amdgpu-backup/bin/" 2>/dev/null || true
|
|
cp -a "$_TC"/lib/libLLVM* "$_TC/.pre-amdgpu-backup/lib/" 2>/dev/null || true
|
|
fi
|
|
rsync -a "$_LC" "$_TC/bin/llvm-config"
|
|
rsync -a "$_ST"/stage/usr/lib/libLLVM.so "$_ST"/stage/usr/lib/libLLVM.so.* "$_ST"/stage/usr/lib/libLLVM-*.so "$_TC/lib/" 2>/dev/null || true
|
|
rsync -a "$_ST"/stage.dev/usr/lib/libLLVM*.a "$_TC/lib/" 2>/dev/null || true
|
|
rsync -a --delete "$_ST"/stage.dev/usr/include/llvm "$_TC/include/" 2>/dev/null || true
|
|
rsync -a --delete "$_ST"/stage.dev/usr/include/llvm-c "$_TC/include/" 2>/dev/null || true
|
|
if "$_TC/bin/llvm-config" --targets-built | grep -qw AMDGPU; then
|
|
echo "${C_INFO}>>>${C_RESET} Toolchain LLVM now provides AMDGPU/NVPTX."
|
|
else
|
|
echo "${C_ERR}>>> ERROR:${C_RESET} AMDGPU splice failed (toolchain still lacks AMDGPU)." >&2
|
|
exit 1
|
|
fi
|
|
else
|
|
echo "${C_ERR}>>> ERROR:${C_RESET} host:llvm21 did not stage an AMDGPU-enabled llvm-config." >&2
|
|
exit 1
|
|
fi
|
|
else
|
|
echo "${C_ERR}>>> ERROR:${C_RESET} host:llvm21 cook failed (required for the Mesa AMDGPU backend)." >&2
|
|
exit 1
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
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
|
|
# kf6 modules pulled by kwin) do find_package(Qt6Qml)/find_package(Qt6Quick).
|
|
# If these are only resolved transitively during the parallel `make live`
|
|
# graph, a consumer can be cooked before qtdeclarative publishes Qt6Qml and
|
|
# configure fails ("Could not find a package configuration file provided by
|
|
# Qt6Qml"), aborting the whole build. Pre-cook the Qt stack IN ORDER, before
|
|
# sddm/kwin, so every Qt6 CMake package is cached in the sysroot first.
|
|
PRECOOK_PKGS="relibc base redoxfs userutils base-initfs icu llvm21 libclc mesa libdrm libepoxy redox-drm lcms2 libdisplay-info libxcvt qtbase qtshadertools qtdeclarative qtsvg qtwayland sddm redbear-compositor redbear-greeter"
|
|
else
|
|
PRECOOK_PKGS="relibc base redoxfs userutils base-initfs icu"
|
|
fi
|
|
# NB: base/redoxfs/userutils are added to the pre-cook set so the boot-ABI
|
|
# "relink ONLY the static initfs-critical binaries" invalidation above
|
|
# (which rm's their repo pkgar + target) reliably re-publishes them via the
|
|
# single-recipe `repo cook` path — the same recovery relibc already relies on.
|
|
# Without this they cook "successful" inside `make live`'s nonstop graph but
|
|
# lose their stage.toml before publish (repo marks them outdated -> installer
|
|
# fails with `Package "base" not found`). The `[ ! -f repo/$pkg.pkgar ]` guard
|
|
# below makes this zero-cost on builds where they were not invalidated.
|
|
# bootstrap is intentionally omitted: it is part of the base recipe, not a
|
|
# standalone cookable package.
|
|
for pkg in $PRECOOK_PKGS; do
|
|
if [ ! -f "$PROJECT_ROOT/repo/x86_64-unknown-redox/$pkg.pkgar" ]; then
|
|
echo " cooking $pkg..."
|
|
log=$(mktemp)
|
|
# Pre-cook offline/upstream policy must match the main build phase
|
|
# (see the `make live` dispatch below). The main phase goes online when
|
|
# EITHER the REDBEAR_ALLOW_UPSTREAM env var is 1 OR the --upstream flag
|
|
# (ALLOW_UPSTREAM=1) is passed; it goes offline in release mode. The
|
|
# pre-cook previously honored only REDBEAR_ALLOW_UPSTREAM, so a plain
|
|
# `--upstream` invocation left the pre-cook OFFLINE while the main phase
|
|
# was ONLINE — any package missing its source.tar cache (e.g. after a
|
|
# version bump) then failed the pre-cook with "Opening file for blake3
|
|
# failed ... source.tar: No such file". Honor ALLOW_UPSTREAM here too.
|
|
if [ "${REDBEAR_ALLOW_UPSTREAM:-0}" = "1" ] || [ "${ALLOW_UPSTREAM:-0}" -eq 1 ]; then
|
|
PRECOOK_OFFLINE=false
|
|
else
|
|
PRECOOK_OFFLINE=true
|
|
fi
|
|
if [ -n "${REDBEAR_RELEASE:-}" ]; then
|
|
PRECOOK_OFFLINE=true
|
|
fi
|
|
if ! REDBEAR_BUILD_PHASE=pre-cook CI=1 COOKBOOK_OFFLINE="$PRECOOK_OFFLINE" \
|
|
"$PROJECT_ROOT/target/release/repo" cook "$pkg" >"$log" 2>&1; then
|
|
echo "WARNING: pre-cook of $pkg failed (non-fatal; will build during main phase). Tail of repo log:" >&2
|
|
tail -50 "$log" >&2
|
|
cp "$log" "$REDBEAR_BUILD_LOGS_DIR/${pkg}.pre-cook.log" 2>/dev/null || true
|
|
rm -f "$log"
|
|
else
|
|
cp "$log" "$REDBEAR_BUILD_LOGS_DIR/${pkg}.pre-cook.log" 2>/dev/null || true
|
|
rm -f "$log"
|
|
fi
|
|
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
|
|
# here in the dispatch script. Specifically the redbear-bare target
|
|
# reads REDBEAR_BARE_INITFS=1 in recipes/core/base-initfs/recipe.toml
|
|
# to switch from a 22-daemon initfs to a 7-daemon minimal initfs
|
|
# (init logd ramfs randd zerod ptyd getty). Auto-set when CONFIG is
|
|
# redbear-bare unless the operator has overridden the env var.
|
|
if [ "$CONFIG" = "redbear-bare" ] && [ -z "${REDBEAR_BARE_INITFS:-}" ]; then
|
|
REDBEAR_BARE_INITFS=1
|
|
export REDBEAR_BARE_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 "${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 "${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 "${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 "${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
|
|
|
|
# --- Critical-package gate --------------------------------------------------
|
|
# cookbook `make live` returns 0 even when individual recipes fail: it builds
|
|
# whatever it can and the installer packages whatever pkgars already exist. A
|
|
# failed critical recipe (e.g. mesa) therefore silently yields an ISO missing
|
|
# the entire desktop stack while this script still prints "Build Complete!".
|
|
# That happened for real: a mesa failure (libclc) cascaded to qtbase, qt*,
|
|
# sddm and redbear-greeter all being absent, yet the build exited 0.
|
|
#
|
|
# Gate on the target's critical packages actually existing as pkgars. If any is
|
|
# missing, fail LOUDLY (non-zero) and list them, so an incomplete desktop build
|
|
# can never masquerade as a working one. Console-only targets have no desktop
|
|
# critical set and are unaffected.
|
|
REPO_ARCH_DIR="$PROJECT_ROOT/repo/x86_64-unknown-redox"
|
|
case "$CONFIG" in
|
|
redbear-full)
|
|
CRITICAL_PKGS="mesa qtbase qtdeclarative qtwayland sddm redbear-compositor redbear-greeter"
|
|
;;
|
|
*)
|
|
CRITICAL_PKGS=""
|
|
;;
|
|
esac
|
|
_missing_critical=""
|
|
for _p in $CRITICAL_PKGS; do
|
|
[ -f "$REPO_ARCH_DIR/$_p.pkgar" ] || _missing_critical="$_missing_critical $_p"
|
|
done
|
|
if [ -n "$_missing_critical" ]; then
|
|
echo "" >&2
|
|
echo "========================================" >&2
|
|
echo " BUILD INCOMPLETE for $CONFIG" >&2
|
|
echo " Critical packages missing (no pkgar):$_missing_critical" >&2
|
|
echo "" >&2
|
|
echo " cookbook 'make live' returned success but these recipes did not" >&2
|
|
echo " produce a package, so the ISO is missing the desktop stack. This is" >&2
|
|
echo " a real failure, not a warning. Inspect the per-recipe logs under" >&2
|
|
echo " $REDBEAR_BUILD_LOGS_DIR and the build output above (search 'failed')." >&2
|
|
echo "========================================" >&2
|
|
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 "${C_WARN}>>> REMINDER:${C_RESET} prefix sysroot is stale. Run 'make prefix' to avoid link errors on next build."
|
|
fi
|
|
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:"
|
|
echo " make qemu QEMUFLAGS=\"-m 4G\""
|
|
ls -lh "$PROJECT_ROOT/build/$ARCH/$CONFIG.iso" 2>/dev/null
|