#!/usr/bin/env bash # rebuild-cascade.sh — Rebuild a package and all packages that depend on it # # Usage: # ./local/scripts/rebuild-cascade.sh [package2 ...] # ./local/scripts/rebuild-cascade.sh --dry-run # # 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] [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] [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}" # Collect all recipe directories RECIPE_DIRS=() for pkg in "${PACKAGES[@]}"; do # Find the recipe directory found=0 for dir in recipes/*/; do if [ -d "${dir}${pkg}" ]; then RECIPE_DIRS+=("${dir}${pkg}") found=1 break fi done # Also check local/recipes if [ $found -eq 0 ]; then for dir in local/recipes/*/; do if [ -d "${dir}${pkg}" ]; then RECIPE_DIRS+=("${dir}${pkg}") found=1 break fi done fi if [ $found -eq 0 ]; then echo "ERROR: recipe not found for package '${pkg}'" >&2 exit 1 fi done # Find all recipes that depend on any of the target packages # by scanning their recipe.toml for dependency entries find_reverse_deps() { local target="$1" local result=() # Search all recipe.toml files for dependencies while IFS= read -r -d '' recipe_toml; do # Get the package name from the directory pkg_dir="$(dirname "${recipe_toml}")" pkg_name="$(basename "${pkg_dir}")" # Skip the target itself if [ "${pkg_name}" = "${target}" ]; then continue fi # Check if this recipe depends on the target if grep -q "dependencies.*=.*\[.*${target}.*\]" "${recipe_toml}" 2>/dev/null; then result+=("${pkg_name}") fi done < <(find recipes/ local/recipes/ -name "recipe.toml" -print0 2>/dev/null) printf '%s\n' "${result[@]}" | sort -u } # 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