#!/usr/bin/env bash set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" source "$SCRIPT_DIR/lib/relibc-surface.sh" # 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 ">>> 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 CONFIG="redbear-full" JOBS="${JOBS:-$(nproc)}" APPLY_PATCHES="${APPLY_PATCHES:-1}" ALLOW_UPSTREAM=0 NO_CACHE=0 usage() { cat <&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) ;; *) echo "ERROR: Unknown config '$CONFIG'" >&2 echo "Supported: redbear-full, redbear-mini, redbear-grub" >&2 exit 1 ;; esac echo "========================================" echo " Red Bear OS Build System" echo "========================================" echo "Config: $CONFIG" echo "Jobs: $JOBS" echo "Apply patches: $APPLY_PATCHES" echo "Upstream: $ALLOW_UPSTREAM" echo "Root: ${PROJECT_ROOT##*/}" echo "========================================" echo "" cd "$PROJECT_ROOT" if [ -x "$PROJECT_ROOT/local/scripts/verify-overlay-integrity.sh" ] && [ -z "${REDBEAR_RELEASE:-}" ]; then echo ">>> Skipping overlay repair (causes recipe symlink corruption)" # "$PROJECT_ROOT/local/scripts/verify-overlay-integrity.sh" --repair || true 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 ">>> 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 stash_nested_repo_if_dirty() { local target_dir="$1" local label="$2" if [ -d "$target_dir/.git" ]; then if ! git -C "$target_dir" diff --quiet || ! git -C "$target_dir" diff --cached --quiet || [ -n "$(git -C "$target_dir" ls-files --others --exclude-standard)" ]; then echo ">>> Stashing dirty nested $label checkout before build..." rm -f "$target_dir/.git/index.lock" git -C "$target_dir" stash push --all -m "build-redbear-auto-stash" > /dev/null 2>&1 || true fi fi } stash_nested_repo_if_dirty "$PROJECT_ROOT/recipes/core/relibc/source" "relibc" if [ "$APPLY_PATCHES" = "1" ] && [ -z "${REDBEAR_RELEASE:-}" ]; then echo ">>> Patches are applied by 'repo fetch' via recipe.toml (atomic mechanism)" echo ">>> Skipping direct patch application (was bypassing cookbook atomicity)" echo "" elif [ -n "${REDBEAR_RELEASE:-}" ]; then echo ">>> Release mode: skipping patch application (patches pre-applied in archived sources)" fi if [ ! -f "target/release/repo" ]; then echo ">>> Building cookbook binary..." cargo build --release 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 ">>> Found $FW_COUNT AMD firmware blobs" else echo ">>> WARNING: 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 ">>> This may take 30-60 minutes 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 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 ">>> Stale $src detected (working tree has uncommitted changes); invalidating..." else echo ">>> 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 fi fi done if [ "$STALE_DETECTED" = "1" ]; then echo ">>> Cleaning stale build/sysroot dirs (low-level runtime changed)..." find "$PROJECT_ROOT/recipes" "$PROJECT_ROOT/local/recipes" \ \( -path "*/target/x86_64-unknown-redox/build" \ -o -path "*/target/x86_64-unknown-redox/sysroot" \) \ -type d -exec rm -rf {} + 2>/dev/null || true fi fi PREFIX_LIBC="$PROJECT_ROOT/prefix/x86_64-unknown-redox/sysroot/x86_64-unknown-redox/lib/libc.a" STALE_PREFIX=0 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") if [ "$fork_ts" != "0" ] && [ "$prefix_ts" != "0" ] && [ "$fork_ts" -gt "$prefix_ts" ]; 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") echo ">>> WARNING: prefix sysroot is stale — $src fork has commits newer than prefix libc.a" echo ">>> Fork commit: $fork_date" echo ">>> Prefix built: $prefix_date" echo ">>> Run 'touch $src && make prefix' to rebuild the toolchain with latest $src changes." echo ">>> Continuing build — packages may fail to link against stale libraries." STALE_PREFIX=1 fi fi done fi if [ "$NO_CACHE" = "1" ]; then echo ">>> 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. echo ">>> Pre-cooking critical packages..." if [ "$CONFIG" = "redbear-full" ]; then PRECOOK_PKGS="relibc icu llvm21 mesa libdrm libepoxy redox-drm lcms2 libdisplay-info libxcvt sddm qtbase kwin" else PRECOOK_PKGS="relibc icu" fi for pkg in $PRECOOK_PKGS; do if [ ! -f "$PROJECT_ROOT/repo/x86_64-unknown-redox/$pkg.pkgar" ]; then echo " cooking $pkg..." log=$(mktemp) if ! COOKBOOK_OFFLINE=false "$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 rm -f "$log" else rm -f "$log" fi fi done for src in relibc kernel base bootloader installer; 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 if [ "${REDBEAR_ALLOW_UPSTREAM:-0}" = "1" ]; then echo ">>> WARNING: Upstream fetch ENABLED (REDBEAR_ALLOW_UPSTREAM=1)" REPO_OFFLINE=0 COOKBOOK_OFFLINE=false CI=1 make live "CONFIG_NAME=$CONFIG" "JOBS=$JOBS" 2>&1 elif [ -n "${REDBEAR_RELEASE:-}" ]; then echo ">>> Release mode: building from immutable archives (offline)" REPO_OFFLINE=1 COOKBOOK_OFFLINE=true CI=1 make live "CONFIG_NAME=$CONFIG" "JOBS=$JOBS" 2>&1 elif [ "$ALLOW_UPSTREAM" -eq 1 ]; then echo ">>> Upstream recipe refresh enabled" REPO_OFFLINE=0 COOKBOOK_OFFLINE=false CI=1 make live "CONFIG_NAME=$CONFIG" "JOBS=$JOBS" 2>&1 else echo ">>> Upstream recipe refresh disabled (default: offline)" REPO_OFFLINE=1 COOKBOOK_OFFLINE=true CI=1 make live "CONFIG_NAME=$CONFIG" "JOBS=$JOBS" 2>&1 fi 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." fi echo "========================================" echo " Build Complete!" echo "========================================" 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