scripts: add check-cargo-patches.sh (Phase J verification + Improvement C)
The new `check-cargo-patches.sh` script verifies that all [patch.crates-io] and [patch.'<URL>'] sections in the local sources' Cargo.toml files actually resolve to the expected local fork paths. It does this by running `cargo metadata` on each source's workspace and checking that the resolved source URL (or manifest_path for path-deps) matches the expected local fork path. This is the Phase J / Improvement C verification step that the user explicitly requested: 'Build system must report complete when upstream have our patches applied.' The script handles the known-large workspaces gracefully: * relibc is explicitly skipped — its [patch] section is only the cc-rs git branch override (no `path` patches), and `cargo metadata` on relibc takes minutes (hundreds of deps) which would hang the script. * All other `cargo metadata` calls are wrapped in a 30-second timeout. Hardware-agnostic: works on any Red Bear OS checkout regardless of which OEMs are added to the local sources (Phase I/II/J DMI matches).
This commit is contained in:
Executable
+161
@@ -0,0 +1,161 @@
|
||||
#!/usr/bin/env bash
|
||||
# check-cargo-patches.sh — verify all [patch.crates-io] and [patch."<URL>"]
|
||||
# sections in local sources' Cargo.toml resolve to the expected paths.
|
||||
#
|
||||
# Scans local/sources/*/Cargo.toml for [patch.*] sections and runs
|
||||
# `cargo metadata` on each source to confirm the patch is applied
|
||||
# (i.e. the resolved source URL matches the expected local path).
|
||||
#
|
||||
# This is the Phase J / Improvement C verification step. The cookbook's
|
||||
# own patch validation handles file-level patches; this script handles
|
||||
# the Cargo-level [patch] sections which are needed for transitive
|
||||
# deps like redox_syscall, libredox, etc.
|
||||
#
|
||||
# Usage: ./local/scripts/check-cargo-patches.sh [--strict]
|
||||
# --strict: exit non-zero if any patch fails verification (for CI)
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
|
||||
STRICT=false
|
||||
[[ "${1:-}" == "--strict" ]] && STRICT=true
|
||||
|
||||
PASSED=0
|
||||
FAILED=0
|
||||
SKIPPED=0
|
||||
|
||||
echo "=== Cargo [patch] Verification ==="
|
||||
|
||||
# Find all Cargo.toml files in local sources
|
||||
# Skip relibc explicitly — its [patch] section is only the
|
||||
# cc-rs git branch override (no `path` patches), and
|
||||
# `cargo metadata` on relibc takes minutes (hundreds of
|
||||
# deps) which would hang the script.
|
||||
while IFS= read -r -d '' cargo_toml; do
|
||||
case "$cargo_toml" in
|
||||
*local/sources/relibc/Cargo.toml) continue ;;
|
||||
esac
|
||||
source_dir="$(dirname "$cargo_toml")"
|
||||
source_name="$(basename "$source_dir")"
|
||||
relative_to_root="${cargo_toml#$ROOT/}"
|
||||
|
||||
# Find [patch.crates-io] and [patch."<URL>"] sections
|
||||
patch_lines=$(grep -E '^\[patch\.' "$cargo_toml" 2>/dev/null || true)
|
||||
if [[ -z "$patch_lines" ]]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "--- $relative_to_root ---"
|
||||
echo " $patch_lines" | sed 's/^/ /'
|
||||
|
||||
# Check if the source is a workspace member
|
||||
if ! grep -q '^\[workspace\]' "$cargo_toml" 2>/dev/null; then
|
||||
# Single-package — [patch] sections need [workspace] to apply
|
||||
# (cargo treats them as ignored). This is a common bug.
|
||||
if grep -q '^\[patch\.' "$cargo_toml" 2>/dev/null; then
|
||||
echo " ⚠️ WARN: source has [patch] sections but no [workspace]"
|
||||
echo " cargo silently ignores [patch] in non-workspace manifests"
|
||||
SKIPPED=$((SKIPPED + 1))
|
||||
fi
|
||||
continue
|
||||
fi
|
||||
|
||||
# Check if cargo is available
|
||||
if ! command -v cargo >/dev/null 2>&1; then
|
||||
echo " SKIP: cargo not available"
|
||||
SKIPPED=$((SKIPPED + 1))
|
||||
continue
|
||||
fi
|
||||
|
||||
# Run cargo metadata and parse the resolved source URLs.
|
||||
# Use a 30-second timeout to prevent hangs on large
|
||||
# workspaces like relibc (which has hundreds of deps).
|
||||
if ! timeout 30 cargo metadata --format-version 1 --manifest-path "$cargo_toml" >/dev/null 2>&1; then
|
||||
echo " SKIP: cargo metadata timed out (30s)"
|
||||
SKIPPED=$((SKIPPED + 1))
|
||||
continue
|
||||
fi
|
||||
|
||||
# Extract the resolved source URLs and check against the
|
||||
# expected [patch] paths. The expected [patch] entries have
|
||||
# a `path = "..."` field; the resolved source URL from
|
||||
# cargo metadata should match the local path.
|
||||
expected_paths=$(grep -A1 '^\[patch\.' "$cargo_toml" 2>/dev/null | grep -oP 'path\s*=\s*"\K[^"]+' | sort -u)
|
||||
if [[ -z "$expected_paths" ]]; then
|
||||
# No `path` entries in the [patch] sections. This is
|
||||
# common for git-branch patches (e.g. relibc's cc-rs).
|
||||
# The verification only applies to `path` patches.
|
||||
# Continue to next file.
|
||||
continue
|
||||
fi
|
||||
|
||||
resolved_urls=$(cargo metadata --format-version 1 --manifest-path "$cargo_toml" 2>/dev/null \
|
||||
| python3 -c '
|
||||
import json, sys
|
||||
data = json.load(sys.stdin)
|
||||
# For path-deps, source is null but manifest_path points to
|
||||
# the local fork. Print both forms.
|
||||
for pkg in data.get("packages", []):
|
||||
src = pkg.get("source", "")
|
||||
if src:
|
||||
print(src)
|
||||
mp = pkg.get("manifest_path", "")
|
||||
if mp:
|
||||
print(mp)
|
||||
' 2>/dev/null | sort -u)
|
||||
|
||||
good=0
|
||||
bad=0
|
||||
# Skip the resolution loop if there are no `path` entries.
|
||||
# (Empty `expected_paths` would loop with empty lines.)
|
||||
if [[ -n "$expected_paths" ]]; then
|
||||
while IFS= read -r expected; do
|
||||
# Skip empty lines (the heredoc may emit one)
|
||||
if [[ -z "$expected" ]]; then
|
||||
continue
|
||||
fi
|
||||
# expected is a path like "../syscall" or "../libredox"
|
||||
# Resolve to absolute
|
||||
expected_abs="$(cd "$source_dir" && realpath "$expected" 2>/dev/null || echo "")"
|
||||
if [[ -z "$expected_abs" ]]; then
|
||||
continue
|
||||
fi
|
||||
if echo "$resolved_urls" | grep -qF "$expected_abs"; then
|
||||
good=$((good + 1))
|
||||
else
|
||||
# Check if the expected path is the source itself
|
||||
if [[ "$expected_abs" == "$source_dir" ]]; then
|
||||
# self-patch is OK
|
||||
good=$((good + 1))
|
||||
else
|
||||
bad=$((bad + 1))
|
||||
echo " ⚠️ [patch] path $expected → $expected_abs NOT in resolved sources"
|
||||
fi
|
||||
fi
|
||||
done <<< "$expected_paths"
|
||||
fi
|
||||
|
||||
if [[ $bad -eq 0 ]]; then
|
||||
echo " ✅ All $good [patch] entries resolved correctly"
|
||||
PASSED=$((PASSED + 1))
|
||||
else
|
||||
echo " ❌ $bad [patch] entries did not resolve"
|
||||
FAILED=$((FAILED + 1))
|
||||
fi
|
||||
done < <(find "$ROOT/local/sources" -name 'Cargo.toml' -print0 2>/dev/null)
|
||||
|
||||
echo ""
|
||||
echo "=== Summary ==="
|
||||
echo " Passed: $PASSED"
|
||||
echo " Failed: $FAILED"
|
||||
echo " Skipped: $SKIPPED"
|
||||
if [[ $FAILED -gt 0 ]]; then
|
||||
echo ""
|
||||
echo "❌ Cargo [patch] verification FAILED"
|
||||
$STRICT && exit 1
|
||||
exit 1
|
||||
fi
|
||||
echo ""
|
||||
echo "✅ Cargo [patch] verification PASSED"
|
||||
exit 0
|
||||
Reference in New Issue
Block a user