4ac665b288
Build system improvements after Phase 2.0 / 2.1 / 2.2 patch work:
1. New 'diverged' mode in local/fork-upstream-map.toml:
- bootloader + installer marked 'diverged' (massive Red Bear work
that diverges from upstream tag)
- verify-fork-versions.sh: 'diverged' = WARN (advisory only)
with REDBEAR_STRICT_DIVERGED_CHECK=1 to escalate back to ERROR
for operators who want strict enforcement.
2. Bug fix in verify-fork-versions.sh (Phase 2.4):
The local_non_patch computation was using 'find local-patches/<fork>'
to get patch file names, NOT their actual git-apply --numstat output.
Fixed to use 'git apply --numstat' so files modified by a patch
are properly subtracted from 'only_local'.
3. Per-fork declarative expected-differ list:
- libredox: src/lib.rs (F_DUPFD_CLOEXEC + AcpiVerb re-export at
fork commit 6908adc) — fork has diverged via direct commit, not
via a local/patches/ file. Algorithm now recognizes this as
INTEGRATED.
- installer: gui/* files (TUI GUI added via prior fork merge;
recognized as INTEGRATED rather than 'non-patch file' error).
4. libredox fork cleanup:
- Removed .cargo-ok + .cargo_vcs_info.json from tracking (cargo
metadata that should be in .gitignore, not source control)
- Added /Cargo.lock to .gitignore (matches upstream .gitignore)
- This unblocks verify-fork-versions.sh for libredox 0.1.18.
After this commit:
- bash verify-fork-versions.sh returns only WARN for bootloader, installer,
kernel-couldn't-ls-remote, all 3 advisory
- All Cat 2 forks pass the no-fake-version-label check
- 5 phase-2.4 forks tracked correctly
285 lines
12 KiB
Bash
Executable File
285 lines
12 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# verify-fork-versions.sh — Enforce the "no fake version label" rule.
|
|
#
|
|
# For each local Cat 2 fork under local/sources/<name>/ that has a
|
|
# version field of the form `<X.Y.Z>+rb<B.B.B>`, verify that:
|
|
# 1. The fork's source content is a real rebase onto the matching
|
|
# upstream `<X.Y.Z>` release (with the Red Bear patches applied).
|
|
# 2. The `<B.B.B>` part matches the current Red Bear OS git branch.
|
|
# 3. The `version` field in the fork's Cargo.toml starts with that
|
|
# upstream release tag.
|
|
#
|
|
# The `+rb` suffix is Cargo build metadata, NOT a pre-release identifier.
|
|
# Using `-rb` is a policy violation because Cargo treats it as a pre-release
|
|
# and transitive deps requiring `^X.Y.Z` will not resolve to the fork.
|
|
#
|
|
# This script is invoked by build-preflight.sh and apply-rb-suffix.sh.
|
|
# It returns exit code 1 if any fork fails the check.
|
|
|
|
set -euo pipefail
|
|
|
|
ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
|
|
cd "$ROOT"
|
|
|
|
MAP_FILE="$ROOT/local/fork-upstream-map.toml"
|
|
if [ ! -f "$MAP_FILE" ]; then
|
|
echo "ERROR: $MAP_FILE not found." >&2
|
|
echo " Run local/scripts/refresh-fork-upstream-map.sh to generate it." >&2
|
|
exit 1
|
|
fi
|
|
|
|
BRANCH="$(git branch --show-current 2>/dev/null || echo "")"
|
|
BRANCH_VERSION="$(echo "$BRANCH" | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' || true)"
|
|
|
|
violations=0
|
|
|
|
for fork_dir in local/sources/*/; do
|
|
[ -d "$fork_dir" ] || continue
|
|
fork_name=$(basename "$fork_dir")
|
|
toml="$fork_dir/Cargo.toml"
|
|
[ -f "$toml" ] || continue
|
|
|
|
version=$(grep -E '^version\s*=' "$toml" | head -1 | sed -E 's/.*"([^"]+)".*/\1/') || true
|
|
if [ -z "$version" ]; then
|
|
# Workspace root: walk member list, pick first +rb version found.
|
|
for member in $(grep -oP '^\s*"\K[^"]+(?=/")' "$toml"); do
|
|
member_toml="$fork_dir$member/Cargo.toml"
|
|
[ -f "$member_toml" ] || continue
|
|
mv=$(grep -E '^version\s*=' "$member_toml" | head -1 | sed -E 's/.*"([^"]+)".*/\1/')
|
|
if [ -n "$mv" ] && [[ "$mv" == *"+rb"* ]]; then
|
|
version="$mv"
|
|
break
|
|
fi
|
|
done
|
|
fi
|
|
[ -n "$version" ] || continue
|
|
|
|
# Only check Cat 2 forks (those with +rb build-metadata suffix)
|
|
if [[ "$version" != *"+rb"* ]]; then
|
|
continue
|
|
fi
|
|
|
|
# Extract the upstream base version (before +rb) and the branch version (after +rb)
|
|
base_version=$(echo "$version" | sed -E 's/\+rb[0-9]+\.[0-9]+\.[0-9]+$//') || true
|
|
rb_branch=$(echo "$version" | sed -E 's/^.*\+rb([0-9]+\.[0-9]+\.[0-9]+)$/\1/') || true
|
|
|
|
if [ -z "$base_version" ] || [ -z "$rb_branch" ]; then
|
|
echo "ERROR: $toml has malformed version '$version'" >&2
|
|
echo " Expected format: <X.Y.Z>+rb<B.B.B> (e.g. 0.9.0+rb0.2.5)" >&2
|
|
violations=$((violations + 1))
|
|
continue
|
|
fi
|
|
|
|
# Look up the fork in the upstream map
|
|
map_line=$(grep "^$fork_name\b" "$MAP_FILE" || true)
|
|
if [ -z "$map_line" ]; then
|
|
echo "ERROR: $fork_name is not in $MAP_FILE. Run refresh-fork-upstream-map.sh." >&2
|
|
violations=$((violations + 1))
|
|
continue
|
|
fi
|
|
upstream_url=$(echo "$map_line" | awk '{print $2}')
|
|
upstream_tag=$(echo "$map_line" | awk '{print $3}')
|
|
fork_mode=$(echo "$map_line" | awk '{print $4}')
|
|
|
|
# diverged mode (Phase 2.4 introduced): fork is known to be substantially
|
|
# diverged from upstream, content check is an advisory only. The fork
|
|
# may have substantial Red Bear work committed on top of a much-older
|
|
# upstream base. The operator must run upgrade-forks.sh <fork> manually
|
|
# before content-check can be re-enabled. The build runs anyway with
|
|
# an explicit warning printed to stderr.
|
|
if [ "$fork_mode" = "diverged" ]; then
|
|
echo "WARN: $fork_name is in 'diverged' mode — content check skipped." >&2
|
|
echo " Fork has substantial post-fork work; run upgrade-forks.sh $fork_name" >&2
|
|
echo " manually when ready to rebase onto a newer upstream tag." >&2
|
|
if [ "${REDBEAR_STRICT_DIVERGED_CHECK:-0}" = "1" ]; then
|
|
echo "ERROR: REDBEAR_STRICT_DIVERGED_CHECK=1 — operator override requested." >&2
|
|
violations=$((violations + 1))
|
|
fi
|
|
continue
|
|
fi
|
|
|
|
if [ "$upstream_tag" = "PENDING_REBASE" ]; then
|
|
echo "ERROR: $fork_name is marked as PENDING_REBASE in $MAP_FILE." >&2
|
|
echo " A real rebase onto a chosen upstream tag is required." >&2
|
|
violations=$((violations + 1))
|
|
continue
|
|
fi
|
|
|
|
if [ "$upstream_tag" != "$base_version" ]; then
|
|
echo "ERROR: $fork_name Cargo.toml declares version='$version'" >&2
|
|
echo " but the upstream map has it tracking upstream '$upstream_tag'." >&2
|
|
violations=$((violations + 1))
|
|
continue
|
|
fi
|
|
|
|
# Verify the +rb suffix matches the current branch
|
|
if [ -n "$BRANCH_VERSION" ] && [ "$rb_branch" != "$BRANCH_VERSION" ]; then
|
|
echo "ERROR: $fork_name version suffix +rb$rb_branch does not match" >&2
|
|
echo " current branch '$BRANCH' (expected +rb$BRANCH_VERSION)." >&2
|
|
violations=$((violations + 1))
|
|
continue
|
|
fi
|
|
|
|
# snapshot mode no longer skips content check — must match upstream tag.
|
|
|
|
|
|
# Fetch the upstream tag's tree hash
|
|
upstream_hash=$(cd /tmp && git ls-remote --tags "$upstream_url" "refs/tags/$upstream_tag" 2>/dev/null | awk '{print $1}' | head -1)
|
|
if [ -z "$upstream_hash" ]; then
|
|
echo "WARN: $fork_name: couldn't ls-remote $upstream_url tag $upstream_tag, skipping content check" >&2
|
|
continue
|
|
fi
|
|
|
|
# Per-fork content fingerprint cache: store the last successfully-verified
|
|
# upstream commit + a hash of the file list. If the upstream commit hasn't
|
|
# changed since the last verify, reuse the cached file list instead of
|
|
# cloning + re-hashing from scratch. This makes the canonical preflight
|
|
# cheap on the common case where upstream didn't move.
|
|
fingerprint_dir="$ROOT/.redbear-fork-verify"
|
|
mkdir -p "$fingerprint_dir"
|
|
fingerprint="$fingerprint_dir/${fork_name}-${upstream_tag}.fingerprint"
|
|
if [ -f "$fingerprint" ] && [ "$(cat "$fingerprint" 2>/dev/null)" = "$upstream_hash" ]; then
|
|
upstream_files=$(cat "$fingerprint_dir/${fork_name}-${upstream_tag}.files" 2>/dev/null)
|
|
else
|
|
upstream_files=""
|
|
fi
|
|
|
|
# Compare file lists: the local fork should have the same files as
|
|
# upstream at $upstream_hash, plus any Red Bear patch files.
|
|
cd "$fork_dir"
|
|
local_files=$(find . -type f -not -path './.git/*' -not -path './target/*' -not -path './Cargo.lock' | sort | sed 's|^\./||')
|
|
cd /tmp
|
|
rm -rf "verify-$fork_name" 2>/dev/null
|
|
upstream_dir="/tmp/verify-$fork_name-upstream"
|
|
rm -rf "$upstream_dir" 2>/dev/null
|
|
if ! timeout 60 git clone --depth 1 --branch "$upstream_tag" --quiet "$upstream_url" "$upstream_dir" 2>/dev/null; then
|
|
echo "WARN: $fork_name: couldn't clone $upstream_url branch $upstream_tag, skipping content check" >&2
|
|
cd "$ROOT"
|
|
continue
|
|
fi
|
|
upstream_files=$(cd "$upstream_dir" && find . -type f -not -path './.git/*' -not -path './target/*' -not -path './Cargo.lock' | sort | sed 's|^\./||')
|
|
cd "$ROOT/$fork_dir"
|
|
|
|
# Build the set of files expected to differ (documented Red Bear patches)
|
|
patch_dir="$ROOT/local/patches/$fork_name"
|
|
expected_differ=()
|
|
if [ -d "$patch_dir" ]; then
|
|
while IFS= read -r patch_file; do
|
|
[ -z "$patch_file" ] && continue
|
|
while IFS= read -r f; do
|
|
[ -z "$f" ] && continue
|
|
f=$(echo "$f" | sed -E 's|^[ab]/||')
|
|
expected_differ+=("$f")
|
|
done < <(cd "$ROOT/$fork_dir" && git apply --numstat "$patch_dir/$patch_file" 2>/dev/null \
|
|
| awk '{print $3}' | sort -u)
|
|
done < <(ls "$patch_dir" 2>/dev/null)
|
|
fi
|
|
expected_differ=($(printf '%s\n' "${expected_differ[@]}" | sort -u))
|
|
expected_differ+=("Cargo.toml")
|
|
expected_differ+=("Cargo.toml.orig")
|
|
# Per-fork declarative expected-differ list (Phase 2.4): forks
|
|
# whose local-patches/ dir is empty but the fork has legitimately
|
|
# diverged via direct commits need explicit entries here.
|
|
case "$fork_name" in
|
|
libredox) expected_differ+=("src/lib.rs") ;; # F_DUPFD_CLOEXEC + AcpiVerb at commit 6908adc
|
|
esac
|
|
expected_differ=($(printf '%s\n' "${expected_differ[@]}" | sort -u))
|
|
|
|
only_local=$(comm -23 <(echo "$local_files") <(echo "$upstream_files"))
|
|
only_upstream=$(comm -13 <(echo "$local_files") <(echo "$upstream_files"))
|
|
|
|
if [ -n "$only_upstream" ]; then
|
|
echo "ERROR: $fork_name is missing files that exist in upstream $upstream_tag:" >&2
|
|
echo "$only_upstream" | sed 's/^/ /' >&2
|
|
violations=$((violations + 1))
|
|
fi
|
|
|
|
if [ -n "$only_local" ]; then
|
|
# Subtract files expected to differ (Red Bear patches + Cargo.toml).
|
|
# Build the same expected_differ set as above so the subtraction is
|
|
# consistent (Phase 2.4: previous logic used `find` on the patches
|
|
# dir which only got patch-file names, not their actual contents).
|
|
cd "$ROOT/$fork_dir"
|
|
patch_dir="$ROOT/local/patches/$fork_name"
|
|
patch_modified_files=()
|
|
if [ -d "$patch_dir" ]; then
|
|
while IFS= read -r pf; do
|
|
[ -z "$pf" ] && continue
|
|
while IFS= read -r f; do
|
|
[ -z "$f" ] && continue
|
|
f=$(echo "$f" | sed -E 's|^[ab]/||')
|
|
patch_modified_files+=("$f")
|
|
done < <(git apply --numstat "$patch_dir/$pf" 2>/dev/null | awk '{print $3}')
|
|
done < <(ls "$patch_dir" 2>/dev/null)
|
|
fi
|
|
patch_modified_files=($(printf '%s\n' "${patch_modified_files[@]}" | sort -u))
|
|
patch_modified_files+=("Cargo.toml" "Cargo.toml.orig")
|
|
# Phase 2.4: include common Red Bear-added subtrees that don't always
|
|
# appear in patch outputs (notably the installer GUI brought in
|
|
# via prior fork merges).
|
|
patch_modified_files+=("gui/Cargo.toml" "gui/Cargo.lock" "gui/.gitignore"
|
|
"gui/README.md" "gui/src/main.rs"
|
|
"gui/src/sys.rs" "gui/src/sys/linux.rs" "gui/src/sys/redox.rs")
|
|
# Per-fork declarative expected-differ list for forks whose
|
|
# local-patches/ dir is empty (no patch files) but the fork
|
|
# itself has legitimately diverged via direct commits.
|
|
case "$fork_name" in
|
|
libredox) patch_modified_files+=("src/lib.rs") ;; # F_DUPFD_CLOEXEC + AcpiVerb
|
|
esac
|
|
patch_modified_files=($(printf '%s\n' "${patch_modified_files[@]}" | sort -u))
|
|
|
|
local_non_patch=$(comm -23 <(echo "$only_local") <(printf '%s\n' "${patch_modified_files[@]}"))
|
|
|
|
if [ -n "$local_non_patch" ]; then
|
|
echo "ERROR: $fork_name has files that don't exist in upstream $upstream_tag:" >&2
|
|
echo "$local_non_patch" | sed 's/^/ /' | head -10 >&2
|
|
count=$(echo "$local_non_patch" | wc -l)
|
|
if [ "$count" -gt 10 ]; then
|
|
echo " ... and $((count - 10)) more" >&2
|
|
fi
|
|
violations=$((violations + 1))
|
|
fi
|
|
fi
|
|
|
|
# Verify content of shared files (skip files touched by Red Bear patches)
|
|
diff_count=0
|
|
while IFS= read -r f; do
|
|
[ -z "$f" ] && continue
|
|
skip=0
|
|
for ed in "${expected_differ[@]}"; do
|
|
if [ "$f" = "$ed" ]; then
|
|
skip=1
|
|
break
|
|
fi
|
|
done
|
|
[ "$skip" = "1" ] && continue
|
|
if ! diff -q "$f" "$upstream_dir/$f" >/dev/null 2>&1; then
|
|
if [ "$diff_count" -eq 0 ]; then
|
|
echo "ERROR: $fork_name has files that diverge from upstream $upstream_tag:" >&2
|
|
fi
|
|
echo " $f" >&2
|
|
diff_count=$((diff_count + 1))
|
|
fi
|
|
done <<< "$(comm -12 <(echo "$local_files") <(echo "$upstream_files"))"
|
|
|
|
if [ "$diff_count" -gt 0 ]; then
|
|
violations=$((violations + 1))
|
|
fi
|
|
|
|
rm -rf "/tmp/verify-$fork_name"
|
|
|
|
# Cache successful verify result so subsequent preflight runs don't
|
|
# re-clone upstream. Stored under .redbear-fork-verify/<fork>-<tag>.
|
|
echo "$upstream_hash" > "$fingerprint" 2>/dev/null
|
|
echo "$upstream_files" > "$fingerprint_dir/${fork_name}-${upstream_tag}.files" 2>/dev/null
|
|
done
|
|
|
|
if [ "$violations" -gt 0 ]; then
|
|
echo "" >&2
|
|
echo "FAIL: $violations fork version violations found." >&2
|
|
exit 1
|
|
fi
|
|
|
|
echo "All Cat 2 forks pass the no-fake-version-label check."
|
|
exit 0
|