From 32403ccf4b68a96fb8a143186438bf7f77e8809c Mon Sep 17 00:00:00 2001 From: vasilito Date: Wed, 1 Jul 2026 15:01:26 +0300 Subject: [PATCH] scripts: add check-cargo-patches.sh (Phase J verification + Improvement C) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The new `check-cargo-patches.sh` script verifies that all [patch.crates-io] and [patch.''] 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). --- local/scripts/check-cargo-patches.sh | 161 +++++++++++++++++++++++++++ 1 file changed, 161 insertions(+) create mode 100755 local/scripts/check-cargo-patches.sh diff --git a/local/scripts/check-cargo-patches.sh b/local/scripts/check-cargo-patches.sh new file mode 100755 index 0000000000..785e7c8381 --- /dev/null +++ b/local/scripts/check-cargo-patches.sh @@ -0,0 +1,161 @@ +#!/usr/bin/env bash +# check-cargo-patches.sh — verify all [patch.crates-io] and [patch.""] +# 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.""] 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