#!/usr/bin/env bash # sync-versions.sh — Synchronise ALL Red Bear crate versions to match the # current git branch. # # Handles BOTH version categories (local/AGENTS.md § "Version conventions"): # # Cat 1 — In-house Red Bear crates (local/recipes/*/source/): # version = "" (e.g. "0.2.5") # # Cat 2 — Upstream Redox forks (local/sources/*/): # version = "-rb" (e.g. "0.9.0-rb0.2.5") # # When the branch changes (e.g. 0.2.5 → 0.2.6), running this script bumps # Cat 1 versions to the new branch version AND updates Cat 2 suffixes to # match the new branch version. The upstream base versions are preserved. # # Exclusions: # - zbus (upstream zbus fork, keeps its own versioning) # - tlc (established project with independent versioning) # - Meson test-case crates # # Usage: # ./local/scripts/sync-versions.sh # Apply # ./local/scripts/sync-versions.sh --check # Check only, exit 1 if drift found set -euo pipefail ROOT="$(cd "$(dirname "$0")/../.." && pwd)" cd "$ROOT" CHECK_ONLY=0 [[ "${1:-}" == "--check" ]] && CHECK_ONLY=1 # ---- Determine target version from branch ---- BRANCH="$(git branch --show-current 2>/dev/null || echo "")" if [[ -z "$BRANCH" ]]; then echo "ERROR: cannot determine current git branch" >&2 exit 1 fi # Extract semantic version from branch name (e.g. "0.2.5" from "0.2.5") TARGET_VERSION="$(echo "$BRANCH" | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' || true)" if [[ -z "$TARGET_VERSION" ]]; then echo "ERROR: branch '$BRANCH' does not contain a semantic version" >&2 exit 1 fi echo "Target version: $TARGET_VERSION (from branch '$BRANCH')" echo "Cat 2 suffix: -rb$TARGET_VERSION" echo "" # ---- Exclusions ---- # Crates that keep their own versioning (not branch-tracked) EXCLUDE_NAMES=( "zbus" # upstream zbus fork "tlc" # established project with independent versioning ) # Paths to exclude (substring match) EXCLUDE_PATHS=( "test cases" # meson test crates "/target/" # build artifacts ) should_exclude() { local name="$1" local path="$2" for ex in "${EXCLUDE_NAMES[@]}"; do [[ "$name" == "$ex" ]] && return 0 done for ex in "${EXCLUDE_PATHS[@]}"; do [[ "$path" == *"$ex"* ]] && return 0 done return 1 } # ---- Helper: extract upstream base from a Cat 2 version ---- # Strips any existing -rb suffix to get the upstream base version. strip_rb_suffix() { echo "$1" | sed -E 's/(-rb[0-9]+(\.[0-9]+\.[0-9]+)?)+$//' } # ---- Phase 1: Cat 1 — In-house crates (local/recipes/*/source/) ---- echo "=== Phase 1: Cat 1 — In-house crates ===" CAT1_CHANGED=0 CAT1_CHECKED=0 CAT1_DRIFT=0 while IFS= read -r f; do name="$(grep '^name = ' "$f" 2>/dev/null | head -1 | sed 's/name = "//;s/"//' || true)" if should_exclude "$name" "$f"; then continue fi CAT1_CHECKED=$((CAT1_CHECKED + 1)) # Case 1: [package] version = "x.y.z" if grep -q '^version = "' "$f" 2>/dev/null; then current="$(grep '^version = "' "$f" | head -1 | sed 's/version = "//;s/"//' || true)" if [[ "$current" != "$TARGET_VERSION" ]]; then if [[ $CHECK_ONLY -eq 1 ]]; then echo " DRIFT: $name $current → $TARGET_VERSION ($f)" CAT1_DRIFT=$((CAT1_DRIFT + 1)) else sed -i "s/^version = \"$current\"/version = \"$TARGET_VERSION\"/" "$f" echo " UPDATED: $name $current → $TARGET_VERSION ($f)" CAT1_CHANGED=$((CAT1_CHANGED + 1)) fi fi fi # Case 2: [workspace.package] version = "x.y.z" if grep -A1 '^\[workspace\.package\]' "$f" 2>/dev/null | grep -q '^version = "' 2>/dev/null; then current="$(grep -A1 '^\[workspace\.package\]' "$f" | grep '^version = "' | sed 's/version = "//;s/"//' || true)" if [[ "$current" != "$TARGET_VERSION" ]]; then if [[ $CHECK_ONLY -eq 1 ]]; then echo " DRIFT (workspace): $current → $TARGET_VERSION ($f)" CAT1_DRIFT=$((CAT1_DRIFT + 1)) else awk -v new="$TARGET_VERSION" ' /^\[workspace\.package\]/ { in_wp=1 } /^\[/ && !/^\[workspace\.package\]/ { in_wp=0 } in_ws==0 && in_wp==1 && /^version = / { print "version = \"" new "\""; next } { print } ' "$f" > "${f}.tmp" && mv "${f}.tmp" "$f" echo " UPDATED (workspace): $current → $TARGET_VERSION ($f)" CAT1_CHANGED=$((CAT1_CHANGED + 1)) fi fi fi done < <(find local/recipes/ -name "Cargo.toml" -not -path "*/target/*" -path "*/source/*" | sort) echo "" # ---- Phase 2: Cat 2 — Upstream forks (local/sources/*/) ---- echo "=== Phase 2: Cat 2 — Upstream forks ===" CAT2_CHANGED=0 CAT2_CHECKED=0 CAT2_DRIFT=0 RB_SUFFIX="-rb${TARGET_VERSION}" update_cat2_version() { local f="$1" local fork_name="$2" if ! grep -qE '^[[:space:]]*\[(package|workspace\.package)\]' "$f" 2>/dev/null; then return 0 fi local current target base current="$(grep -E '^version\s*=' "$f" | head -1 | sed 's/.*"\(.*\)".*/\1/' || true)" [[ -n "$current" ]] || return 0 # Strip any existing -rb suffix to get upstream base base="$(strip_rb_suffix "$current")" target="${base}${RB_SUFFIX}" if [[ "$current" == "$target" ]]; then return 0 fi CAT2_CHECKED=$((CAT2_CHECKED + 1)) if [[ $CHECK_ONLY -eq 1 ]]; then echo " DRIFT: $fork_name $current → $target ($f)" CAT2_DRIFT=$((CAT2_DRIFT + 1)) else sed -i "s|^version = \"${current}\"|version = \"${target}\"|" "$f" echo " UPDATED: $fork_name $current → $target ($f)" CAT2_CHANGED=$((CAT2_CHANGED + 1)) fi } for fork_dir in local/sources/*/; do [ -d "$fork_dir" ] || continue fork_name=$(basename "$fork_dir") # Skip non-submodule dirs (ctrlc, libpciaccess, redox-drm, sysinfo — empty stubs) [ -f "${fork_dir}Cargo.toml" ] || continue # Try root Cargo.toml first update_cat2_version "${fork_dir}Cargo.toml" "$fork_name" done echo "" # ---- Summary ---- if [[ $CHECK_ONLY -eq 1 ]]; then echo "Cat 1: checked $CAT1_CHECKED, drift $CAT1_DRIFT" echo "Cat 2: checked $CAT2_CHECKED, drift $CAT2_DRIFT" TOTAL_DRIFT=$((CAT1_DRIFT + CAT2_DRIFT)) if [[ $TOTAL_DRIFT -gt 0 ]]; then echo "FAIL: $TOTAL_DRIFT version drift(s) found" exit 1 fi echo "All crates are at correct versions for branch '$BRANCH'" else echo "Cat 1: updated $CAT1_CHANGED (checked $CAT1_CHECKED)" echo "Cat 2: updated $CAT2_CHANGED" echo "All versions synced to branch '$BRANCH'" fi