Files
RedBear-OS/local/scripts/rebuild-cascade.sh
T
vasilito ffbe098ef8 config: add tlc to redbear-mini and redbear-full; create recipe symlink
TLC (Twilight Commander) was missing from both ISO configs. Added
tlc = {} to [packages] in redbear-mini.toml and redbear-full.toml.
Created missing symlink: recipes/tui/tlc -> ../../local/recipes/tui/tlc.
2026-06-19 11:47:25 +03:00

285 lines
8.2 KiB
Bash
Executable File

#!/usr/bin/env bash
# rebuild-cascade.sh — Rebuild a package and all packages that depend on it
#
# Usage:
# ./local/scripts/rebuild-cascade.sh <package> [package2 ...]
# ./local/scripts/rebuild-cascade.sh --dry-run <package>
#
# When a low-level package (e.g. relibc) changes, every package that
# transitively depends on it must be rebuilt. This script:
# 1. Identifies all packages that depend on the target(s)
# 2. Cooks the target package(s) first
# 3. Cooks all dependents in dependency order
# 4. Pushes all rebuilt packages to the sysroot
#
# A package is considered a dependent if its recipe.toml lists the target
# in its [package].dependencies array, OR if its recipe.toml has
# dependencies = [...] that includes the target.
#
# Exit codes:
# 0 — all packages rebuilt and pushed successfully
# 1 — one or more packages failed to build
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ROOT_DIR="$(cd "${SCRIPT_DIR}/../.." && pwd)"
REPO_BIN="${ROOT_DIR}/target/release/repo"
DRY_RUN=0
PACKAGES=()
for arg in "$@"; do
case "$arg" in
--dry-run|-n)
DRY_RUN=1
;;
--help|-h)
echo "Usage: $0 [--dry-run] <package> [package2 ...]"
echo ""
echo "Rebuilds the named package(s) and all packages that transitively"
echo "depend on them, then pushes all to the sysroot."
exit 0
;;
-*)
echo "Unknown option: $arg" >&2
exit 1
;;
*)
PACKAGES+=("$arg")
;;
esac
done
if [ ${#PACKAGES[@]} -eq 0 ]; then
echo "Usage: $0 [--dry-run] <package> [package2 ...]" >&2
exit 1
fi
# Build the repo binary if needed
if [ ! -x "${REPO_BIN}" ]; then
echo "Building repo binary..."
(cd "${ROOT_DIR}" && cargo build --release --bin repo)
fi
cd "${ROOT_DIR}"
# Precompute the entire dependency graph in a single pass. With 3000+ recipes,
# doing per-target awk invocations in the BFS loop is O(N²) and unworkable.
# Precompute once, query in O(1). Function definitions must come before
# their use; place them here so the index build below can call them.
extract_recipe_deps() {
local pkg_dir="$1"
local recipe_toml="${pkg_dir}/recipe.toml"
local deps=""
if [ ! -f "${recipe_toml}" ]; then
echo ""
return
fi
# Both [package] and [build] sections contribute deps. The cookbook's
# get_build_deps_recursive() walks both; this script must match.
local in_section=""
local in_deps=0
while IFS= read -r line; do
if [[ "${line}" =~ ^[[:space:]]*\[([^]]+)\][[:space:]]*$ ]]; then
local sect="${BASH_REMATCH[1]// /}"
if [ "${sect}" = "package" ] || [ "${sect}" = "build" ]; then
in_section="${sect}"
else
in_section=""
fi
in_deps=0
continue
fi
[ -z "${in_section}" ] && continue
if [ "${in_deps}" = 1 ]; then
local item="${line}"
item="${item## }"; item="${item%% }"
item="${item#\"}"; item="${item%\"}"
item="${item%,}"
[ -n "${item}" ] && deps+="${item},"
if [[ "${line}" =~ ^[[:space:]]*\] ]]; then
in_deps=0
fi
continue
fi
if [[ "${line}" =~ ^[[:space:]]*(dependencies|dev_dependencies)[[:space:]]*=[[:space:]]*\[ ]]; then
in_deps=1
local inline="${line#*=}"
inline="${inline#[}"
if [[ "${inline}" == *\]* ]]; then
in_deps=0
inline="${inline%]*}"
for item in ${inline//,/ }; do
item="${item## }"; item="${item%% }"
item="${item#\"}"; item="${item%\"}"
[ -n "${item}" ] && deps+="${item},"
done
fi
fi
done < "${recipe_toml}"
echo "${deps}"
}
recipe_source_dir() {
local pkg_dir="$1"
local recipe_toml="${pkg_dir}/recipe.toml"
[ -f "${recipe_toml}" ] || return 0
local rel
rel="$(awk '/^\[source\]/{flag=1; next} /^\[/{flag=0} flag' \
"${recipe_toml}" 2>/dev/null | \
awk -F'=' '/^path[[:space:]]*=/{gsub(/[" ]/, "", $2); print $2; exit}')"
if [ -n "${rel}" ]; then
(cd "${pkg_dir}" && cd "${rel}" 2>/dev/null && pwd)
fi
}
mapfile -t ALL_RECIPE_TOMLS < <(find recipes/ local/recipes/ -name "recipe.toml" 2>/dev/null)
echo "Cached ${#ALL_RECIPE_TOMLS[@]} recipe.toml paths"
# recipe_index maps pkg_name -> "pkg_dir|recipe_toml|depends_csv"
declare -A recipe_index=()
for recipe_toml in "${ALL_RECIPE_TOMLS[@]}"; do
pkg_dir="$(dirname "${recipe_toml}")"
pkg_name="$(basename "${pkg_dir}")"
deps="$(extract_recipe_deps "${pkg_dir}")"
recipe_index["${pkg_name}"]="${pkg_dir}|${recipe_toml}|${deps}"
done
# Find all recipes that depend on the target by querying the precomputed
# index. Returns newline-separated pkg names that depend on target.
find_reverse_deps() {
local target="$1"
local result=()
# Empty target would match every empty-deps entry — reject early.
if [ -z "${target}" ]; then
return 0
fi
local pkg_name entry
for pkg_name in "${!recipe_index[@]}"; do
if [ "${pkg_name}" = "${target}" ]; then
continue
fi
entry="${recipe_index[${pkg_name}]}"
# entry format: "pkg_dir|recipe_toml|deps_csv"
local deps_csv="${entry##*|}"
if [[ ",${deps_csv}," == *",${target},"* ]]; then
result+=("${pkg_name}")
fi
done
if [ ${#result[@]} -gt 0 ]; then
printf '%s\n' "${result[@]}" | sort -u
fi
}
# Validate that the requested package names exist in the index
for pkg in "${PACKAGES[@]}"; do
if [ -z "${recipe_index[${pkg}]+_}" ]; then
echo "ERROR: recipe not found for package '${pkg}'" >&2
exit 1
fi
done
# Build a complete cascade set using BFS
echo "=== Analyzing dependency cascade ==="
CASCADE=()
VISITED=()
QUEUE=("${PACKAGES[@]}")
while [ ${#QUEUE[@]} -gt 0 ]; do
current="${QUEUE[0]}"
QUEUE=("${QUEUE[@]:1}")
# Skip if already visited
for v in "${VISITED[@]}"; do
if [ "$v" = "$current" ]; then
continue 2
fi
done
VISITED+=("$current")
# Find packages that depend on current
mapfile -t rdeps < <(find_reverse_deps "$current" 2>/dev/null || true)
for dep in "${rdeps[@]}"; do
CASCADE+=("${dep}")
QUEUE+=("${dep}")
done
done
# Remove duplicates and sort
UNIQUE_CASCADE=($(printf '%s\n' "${CASCADE[@]}" | sort -u))
# Build order: cook the target packages first, then dependents
BUILD_ORDER=("${PACKAGES[@]}" "${UNIQUE_CASCADE[@]}")
TOTAL=${#BUILD_ORDER[@]}
echo ""
echo "=== Cascade rebuild plan (${TOTAL} packages) ==="
for i in "${!BUILD_ORDER[@]}"; do
pkg="${BUILD_ORDER[$i]}"
if printf '%s\n' "${PACKAGES[@]}" | grep -q "^${pkg}$"; then
echo " [$((i+1))/${TOTAL}] ${pkg} (ROOT)"
else
echo " [$((i+1))/${TOTAL}] ${pkg} (dependent)"
fi
done
if [ $DRY_RUN -eq 1 ]; then
echo ""
echo "=== Dry run — no packages will be built ==="
exit 0
fi
echo ""
echo "=== Starting cascade rebuild ==="
FAILED=()
BUILT=()
export REDOXER_TOOLCHAIN="${ROOT_DIR}/prefix/x86_64-unknown-redox/relibc-install"
for i in "${!BUILD_ORDER[@]}"; do
pkg="${BUILD_ORDER[$i]}"
echo ""
echo "--- [$((i+1))/${TOTAL}] Building ${pkg} ---"
if "${REPO_BIN}" cook "${pkg}"; then
BUILT+=("${pkg}")
echo "--- ${pkg} built successfully ---"
else
FAILED+=("${pkg}")
echo "ERROR: ${pkg} failed to build" >&2
echo "Continuing with remaining packages..."
fi
done
echo ""
echo "=== Pushing ${#BUILT[@]} built packages to sysroot ==="
for pkg in "${BUILT[@]}"; do
echo "Pushing ${pkg}..."
"${REPO_BIN}" push "${pkg}" || echo "WARNING: push ${pkg} failed"
done
echo ""
echo "=== Cascade rebuild summary ==="
echo " Built: ${#BUILT[@]}/${TOTAL}"
echo " Failed: ${#FAILED[@]}/${TOTAL}"
if [ ${#FAILED[@]} -gt 0 ]; then
echo " Failed packages:"
for pkg in "${FAILED[@]}"; do
echo " - ${pkg}"
done
exit 1
fi
echo " All packages rebuilt and pushed successfully."
exit 0