f7c3d504dd
Per local/AGENTS.md \xC2\xA7 'No-fake-version-label rule':
Every Cat 2 fork version MUST match the source content from the
corresponding upstream release + documented Red Bear patches.
A `-rbN` label on stale content is a fake label and a policy
violation.
Changes:
* local/AGENTS.md: documented the no-fake-version-label rule,
including what counts as a fake label, the enforcement contract,
and what a real Red Bear fork looks like.
* local/fork-upstream-map.toml: authoritative mapping of each
Cat 2 fork (syscall, libredox, redoxfs, redox-scheme, relibc,
kernel, bootloader, installer, userutils) to its upstream Git
URL and release tag.
* local/scripts/refresh-fork-upstream-map.sh: auto-update the
fork-upstream-map by querying each upstream repo for the
current latest stable release tag.
* local/scripts/verify-fork-versions.sh: preflight enforcement
script. For each Cat 2 fork with a `-rbN` version field:
1. Compare fork's file list and content against the upstream
release tag from the map. Reject the build if files are
missing (would be a fake label).
2. Reject the build if files exist in local that don't exist
in upstream (must be moved to local/patches/<fork>/ as
documented Red Bear patches).
3. Reject the build if shared files diverge in content.
* local/scripts/apply-rb-suffix.sh: invokes
verify-fork-versions.sh after applying the `-rbN` label so the
build fails fast if the labelled content is fake.
* local/scripts/build-preflight.sh: invokes
verify-fork-versions.sh at the start of every build. Bypassed
only with REDBEAR_SKIP_FORK_VERIFY=1 (emergency only).
* local/patches/bottom/0001-ratui-0.30-braille-compat.patch: the
ratatui 0.30+ compatibility shim for bottom 0.11.2.
This is the structural enforcement that prevents fake labels from
ever reaching the build again. The current 6 forks with `-rbN`
labels are flagged by the verifier — they must be rebased onto
their actual upstream release before the build can succeed.
299 lines
11 KiB
Bash
Executable File
299 lines
11 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# apply-rb-suffix.sh — Enforce the -rb<N> version convention on all
|
|
# local upstream forks (Category 2 per local/AGENTS.md).
|
|
#
|
|
# Policy (local/AGENTS.md § "Category 2 — Local forks of upstream packages"
|
|
# and § "No-fake-version-label rule"):
|
|
#
|
|
# Every Cat 2 fork version MUST be `<upstream-tag>-rb<N>` where:
|
|
# <upstream-tag> = the upstream version the fork currently tracks
|
|
# <N> = a Red Bear incremental counter, starting at 1 for
|
|
# a new fork, bumped every time Red Bear additions
|
|
# are applied on top of a new upstream commit.
|
|
#
|
|
# The `-rb<N>` part is a Cargo pre-release identifier. Per SemVer and
|
|
# Cargo's version-selection rules, a pre-release version (e.g. 0.9.0-rb1)
|
|
# is NEVER substituted for the corresponding stable version (0.9.0).
|
|
# This guarantees fail-fast when a transitive crate pulls upstream from
|
|
# crates.io while a workspace depends on the local fork.
|
|
#
|
|
# What this script does:
|
|
#
|
|
# 1. Updates `version = "X.Y.Z"` in every `local/sources/*/Cargo.toml`
|
|
# to `"X.Y.Z-rb1"`. The base tag is detected from the current
|
|
# version field; the rb counter defaults to 1 but can be overridden
|
|
# via `--rb=<N>`.
|
|
#
|
|
# 2. Updates path-dep `version = "X.Y.Z"` requirements in every
|
|
# workspace that depends on the local fork (e.g. `base/Cargo.toml`,
|
|
# `libredox/Cargo.toml`, every `local/recipes/*/source/Cargo.toml`).
|
|
# Without this, Cargo will refuse to use `X.Y.Z-rb1` for a path
|
|
# that requires `"X.Y.Z"`.
|
|
#
|
|
# 3. (Optional) Adds `[patch.crates-io]` entries in workspace
|
|
# Cargo.toml files for every Cat 2 fork, so that crates.io
|
|
# transitive references to upstream `X.Y.Z` are also redirected
|
|
# to the local fork. This is necessary because pre-release
|
|
# versions don't match non-pre-release requirements.
|
|
#
|
|
# 4. Verifies that all in-source references are consistent and
|
|
# reports any policy violations (Cat 1 version drift, missing
|
|
# -rb suffix on Cat 2, etc).
|
|
#
|
|
# Usage:
|
|
# ./local/scripts/apply-rb-suffix.sh # Apply -rb1 to all
|
|
# ./local/scripts/apply-rb-suffix.sh --rb=5 # Apply -rb5 to all
|
|
# ./local/scripts/apply-rb-suffix.sh --check # Verify only
|
|
# ./local/scripts/apply-rb-suffix.sh --rollback # Revert to stable X.Y.Z
|
|
|
|
set -euo pipefail
|
|
|
|
ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
|
|
cd "$ROOT"
|
|
|
|
CHECK_ONLY=0
|
|
ROLLBACK=0
|
|
RB_N="1"
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--rb=*) RB_N="${1#*=}" ;;
|
|
--check) CHECK_ONLY=1 ;;
|
|
--rollback) ROLLBACK=1 ;;
|
|
*) echo "Unknown option: $1" >&2; exit 1 ;;
|
|
esac
|
|
shift
|
|
done
|
|
|
|
if [[ $CHECK_ONLY -eq 1 ]] && [[ $ROLLBACK -eq 1 ]]; then
|
|
echo "ERROR: --check and --rollback are mutually exclusive" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# ---- List of Cat 2 forks ----
|
|
#
|
|
# Path under local/sources/. Order matters: dep ordering (syscall first,
|
|
# then libredox which depends on syscall, then everything else).
|
|
CAT2_FORKS=(
|
|
syscall
|
|
libredox
|
|
relibc
|
|
kernel
|
|
base
|
|
bootloader
|
|
installer
|
|
redoxfs
|
|
userutils
|
|
)
|
|
|
|
# ---- Read current version from a Cat 2 fork's Cargo.toml ----
|
|
get_current_version() {
|
|
local fork="$1"
|
|
local f="local/sources/${fork}/Cargo.toml"
|
|
[[ -f "$f" ]] || { echo "MISSING"; return 0; }
|
|
grep -E '^version\s*=' "$f" | head -1 | sed 's/.*"\(.*\)".*/\1/'
|
|
}
|
|
|
|
# ---- Determine target version for a fork ----
|
|
# If ROLLBACK is set: strip any -rb<N> suffix
|
|
# Else: append -rb<RB_N> to the stable base
|
|
compute_target_version() {
|
|
local current="$1"
|
|
# Strip any existing -rb<N> or -pre suffix to get the base
|
|
local base
|
|
base="$(echo "$current" | sed -E 's/-[a-zA-Z0-9.\-]+$//')"
|
|
if [[ $ROLLBACK -eq 1 ]]; then
|
|
echo "$base"
|
|
else
|
|
echo "${base}-rb${RB_N}"
|
|
fi
|
|
}
|
|
|
|
# ---- Update a single fork's Cargo.toml version ----
|
|
update_fork_version() {
|
|
local fork="$1"
|
|
local f="local/sources/${fork}/Cargo.toml"
|
|
if [[ ! -f "$f" ]]; then
|
|
echo " SKIP ${fork}: no Cargo.toml"
|
|
return 0
|
|
fi
|
|
# Workspace-only roots (no [package], no [workspace.package]) have no
|
|
# version to bump. The fork's individual member crates each carry
|
|
# their own version.
|
|
if ! grep -qE '^[[:space:]]*\[(package|workspace\.package)\]' "$f"; then
|
|
echo " SKIP ${fork}: workspace-only root (no version field)"
|
|
return 0
|
|
fi
|
|
local current target
|
|
current="$(get_current_version "$fork")"
|
|
if [[ -z "$current" ]]; then
|
|
echo " SKIP ${fork}: empty version field"
|
|
return 0
|
|
fi
|
|
target="$(compute_target_version "$current")"
|
|
if [[ "$current" == "$target" ]]; then
|
|
echo " OK ${fork}: version already ${target}"
|
|
return 0
|
|
fi
|
|
if [[ $CHECK_ONLY -eq 1 ]]; then
|
|
echo " DRIFT ${fork}: ${current} → ${target}"
|
|
return 1
|
|
fi
|
|
sed -i "s|^version = \"${current}\"|version = \"${target}\"|" "$f"
|
|
echo " SET ${fork}: ${current} → ${target}"
|
|
}
|
|
|
|
# ---- Find all Cargo.toml that reference a Cat 2 fork by version requirement ----
|
|
find_referencing_files() {
|
|
local fork="$1"
|
|
local target="$2"
|
|
# Search for any reference to a package name matching the fork
|
|
# (or its renamed package, like redox_syscall for syscall).
|
|
local package_names
|
|
case "$fork" in
|
|
syscall) package_names=("redox_syscall" "syscall") ;;
|
|
*) package_names=("$fork") ;;
|
|
esac
|
|
for name in "${package_names[@]}"; do
|
|
# Cargo dep lines: name = { ... version = "X.Y.Z" ... }
|
|
grep -rln --include="*.toml" \
|
|
-E "^${name}\s*=\s*\{[^}]*version\s*=" \
|
|
. 2>/dev/null \
|
|
| grep -v "/target/" \
|
|
| grep -v "/.git/"
|
|
done | sort -u
|
|
}
|
|
|
|
# ---- Update version requirements for a fork across all referencing files ----
|
|
# Args: <fork-name> <old-base-version> <new-target-version>
|
|
update_referencing_files() {
|
|
local fork="$1"
|
|
local old_base="$2"
|
|
local new_target="$3"
|
|
local files
|
|
files="$(find_referencing_files "$fork" "$new_target")"
|
|
if [[ -z "$files" ]]; then
|
|
return 0
|
|
fi
|
|
echo " DEBUG update_referencing_files: files=$files" >&2
|
|
# We need to update version requirements from "<old_base>" or
|
|
# "<old_base>-*" to "<new_target>". The trick is to be precise:
|
|
# we update the version field inside `{...}` blocks for THIS fork.
|
|
local package_names
|
|
case "$fork" in
|
|
syscall) package_names=("redox_syscall" "syscall") ;;
|
|
*) package_names=("$fork") ;;
|
|
esac
|
|
for f in $files; do
|
|
local changed=0
|
|
for name in "${package_names[@]}"; do
|
|
if grep -qE "^${name}\s*=\s*\{" "$f"; then
|
|
local tmp
|
|
tmp="$(mktemp)"
|
|
awk -v name="$name" -v old="$old_base" -v new="$new_target" '
|
|
BEGIN { in_block=0; depth=0 }
|
|
{
|
|
if (in_block) {
|
|
n_open = gsub(/\{/, "{")
|
|
n_close = gsub(/\}/, "}")
|
|
depth += n_open - n_close
|
|
if (depth == 0) {
|
|
in_block=0
|
|
print
|
|
next
|
|
}
|
|
if ($0 ~ /version[[:space:]]*=/) {
|
|
gsub("version[[:space:]]*=[[:space:]]*\"" old "\"", "version = \"" new "\"")
|
|
}
|
|
print
|
|
next
|
|
}
|
|
if ($0 ~ "^"name"[[:space:]]*=[[:space:]]*\\{") {
|
|
in_block=1
|
|
depth=1
|
|
# Handle the entry line itself: it may carry
|
|
# the version= we want to update.
|
|
n_open = gsub(/\{/, "{")
|
|
n_close = gsub(/\}/, "}")
|
|
depth += n_open - n_close
|
|
if (depth == 0) {
|
|
in_block=0
|
|
print
|
|
next
|
|
}
|
|
if ($0 ~ /version[[:space:]]*=/) {
|
|
gsub("version[[:space:]]*=[[:space:]]*\"" old "\"", "version = \"" new "\"")
|
|
}
|
|
print
|
|
next
|
|
}
|
|
print
|
|
}
|
|
' "$f" > "$tmp" && mv "$tmp" "$f"
|
|
changed=1
|
|
fi
|
|
done
|
|
if [[ $changed -eq 1 ]]; then
|
|
echo " REQ ${f}: ${old_base} → ${new_target}"
|
|
fi
|
|
done
|
|
}
|
|
|
|
# ---- Phase 1: bump fork versions ----
|
|
echo "=== Phase 1: update Cat 2 fork versions ==="
|
|
DRIFT=0
|
|
for fork in "${CAT2_FORKS[@]}"; do
|
|
if ! update_fork_version "$fork"; then
|
|
DRIFT=$((DRIFT + 1))
|
|
fi
|
|
done
|
|
echo ""
|
|
|
|
# ---- Phase 1.5: enforce no-fake-version-label rule ----
|
|
# Per local/AGENTS.md § "No-fake-version-label rule", a `-rbN` version
|
|
# label is only valid when the source content matches the corresponding
|
|
# upstream release + documented Red Bear patches. Verify that the
|
|
# contents we just labelled actually correspond to the declared
|
|
# upstream tag before we declare success. Refuse to leave the working
|
|
# tree in a state where the version label is fake.
|
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
if [ -x "$SCRIPT_DIR/verify-fork-versions.sh" ]; then
|
|
if ! "$SCRIPT_DIR/verify-fork-versions.sh" 2>&1 | grep -q "^All Cat 2 forks pass"; then
|
|
echo "ERROR: After applying -rbN suffix, verify-fork-versions.sh found fake labels." >&2
|
|
echo " The local source content does not match the declared upstream release." >&2
|
|
echo " This is the no-fake-version-label policy violation." >&2
|
|
echo " Inspect the upstream tag/branch mapping in local/fork-upstream-map.toml," >&2
|
|
echo " update the fork's source content, then re-run apply-rb-suffix.sh." >&2
|
|
if [ "$CHECK_ONLY" != "1" ]; then
|
|
exit 1
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
# ---- Phase 2: update referencing files ----
|
|
echo "=== Phase 2: update version requirements in path-dep consumers ==="
|
|
for fork in "${CAT2_FORKS[@]}"; do
|
|
current="$(get_current_version "$fork")"
|
|
[[ "$current" == "MISSING" ]] && continue
|
|
new_target="$current"
|
|
old_base="$(echo "$current" | sed -E 's/-rb[0-9]+$//')"
|
|
update_referencing_files "$fork" "$old_base" "$new_target"
|
|
done
|
|
echo ""
|
|
|
|
# ---- Phase 3: summary ----
|
|
if [[ $CHECK_ONLY -eq 1 ]]; then
|
|
if [[ $DRIFT -gt 0 ]]; then
|
|
echo "FAIL: $DRIFT fork(s) out of -rb suffix policy"
|
|
exit 1
|
|
fi
|
|
echo "PASS: all Cat 2 forks have -rb<RB_N> suffix"
|
|
exit 0
|
|
fi
|
|
|
|
if [[ $ROLLBACK -eq 1 ]]; then
|
|
echo "Rolled back Cat 2 forks to stable X.Y.Z (no -rb suffix)"
|
|
else
|
|
echo "Applied -rb${RB_N} suffix to all Cat 2 forks"
|
|
fi
|