285 lines · 10.8 KB · Shell
RawDownload
#!/bin/bash# patch-std.sh — Apply wasmVM patches to the Rust std source tree## Patches modify the WASI platform implementation in std to support# process spawning, pipes, user/group IDs, and terminal detection# via custom host_process/host_user WASM imports.## Usage:#   ./scripts/patch-std.sh [--check] [--reverse]## Options:#   --check    Dry-run: verify patches apply cleanly without modifying files#   --reverse  Reverse (unapply) previously applied patches set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"WASMCORE_DIR="$(dirname "$SCRIPT_DIR")"PATCHES_DIR="${PATCH_STD_PATCHES_DIR:-$WASMCORE_DIR/std-patches}" # Resolve from the toolchain directory so this script honors its colocated# rust-toolchain.toml even when invoked from the repository root.SYSROOT=""if [ -n "${PATCH_STD_SOURCE_ROOT:-}" ]; then    STD_SRC="$PATCH_STD_SOURCE_ROOT"else    SYSROOT="$(cd "$WASMCORE_DIR" && rustc --print sysroot)"    STD_SRC="$SYSROOT/lib/rustlib/src/rust"fi if [ ! -d "$STD_SRC/library/std" ]; then    echo "ERROR: Rust source not found at $STD_SRC"    echo "Ensure rust-src component is installed: rustup component add rust-src"    exit 1fi # Parse argumentsMODE="apply"PATCH_FLAGS="-p1"for arg in "$@"; do    case "$arg" in        --check)            MODE="check"            ;;        --reverse)            MODE="reverse"            ;;        *)            echo "Unknown argument: $arg"            echo "Usage: $0 [--check] [--reverse]"            exit 1            ;;    esacdone # Find std patch files in order (reversed for --reverse mode).# Only top-level std-patches/*.patch are std-source patches; subdirectories# (std-patches/crates/*, std-patches/wasi-libc/*) target vendored crates and wasi-libc# and must NOT be applied to the Rust std source tree, so use -maxdepth 1.if [ "$MODE" = "reverse" ]; then    PATCH_FILES=$(find "$PATCHES_DIR" -maxdepth 1 -name '*.patch' -type f 2>/dev/null | sort -r)else    PATCH_FILES=$(find "$PATCHES_DIR" -maxdepth 1 -name '*.patch' -type f 2>/dev/null | sort)fi if [ -z "$PATCH_FILES" ]; then    echo "No patch files found in $PATCHES_DIR"    exit 0fi PATCH_COUNT=$(echo "$PATCH_FILES" | wc -l) # `patch-std` mutates rustup's installed rust-src tree. Detect the two healthy# states (the complete series is already applied, or the complete series applies# to a pristine tree) in an isolated copy. Refresh rustup only for a malformed or# partially applied tree. This retains corruption recovery without downloading# and reinstalling the pinned toolchain on every single-command build.if [ "$MODE" = "apply" ] && [ -z "${PATCH_STD_SOURCE_ROOT:-}" ] && command -v rustup >/dev/null 2>&1; then    TOOLCHAIN="$(basename "$SYSROOT")"    case "$SYSROOT" in        */.rustup/toolchains/*)            STATE_SRC="$(mktemp -d "${TMPDIR:-/tmp}/agentos-std-patch-state.XXXXXX")"            cp -a "$STD_SRC/." "$STATE_SRC/"            FULLY_PATCHED=1            while IFS= read -r PATCH; do                if patch --batch --dry-run -R $PATCH_FLAGS -d "$STATE_SRC" < "$PATCH" > /dev/null 2>&1; then                    patch --batch -R $PATCH_FLAGS -d "$STATE_SRC" < "$PATCH" > /dev/null 2>&1                else                    FULLY_PATCHED=0                    break                fi            done < <(printf '%s\n' "$PATCH_FILES" | sort -r)             if [ "$FULLY_PATCHED" -eq 1 ]; then                echo "Rust std patch series is already fully applied; reusing $TOOLCHAIN."            else                rm -rf "$STATE_SRC"                STATE_SRC="$(mktemp -d "${TMPDIR:-/tmp}/agentos-std-patch-state.XXXXXX")"                cp -a "$STD_SRC/." "$STATE_SRC/"                PRISTINE=1                while IFS= read -r PATCH; do                    if patch --batch --dry-run $PATCH_FLAGS -d "$STATE_SRC" < "$PATCH" > /dev/null 2>&1; then                        patch --batch $PATCH_FLAGS -d "$STATE_SRC" < "$PATCH" > /dev/null 2>&1                    else                        PRISTINE=0                        break                    fi                done < <(printf '%s\n' "$PATCH_FILES" | sort)                 if [ "$PRISTINE" -eq 1 ]; then                    echo "Rust std source is pristine; reusing $TOOLCHAIN."                else                    echo "Rust std source is partially patched or malformed; refreshing $TOOLCHAIN..."                    rm -rf "$SYSROOT"                    rustup toolchain install "$TOOLCHAIN" \                        --profile minimal \                        --component rust-src \                        --target wasm32-wasip1 \                        --force >/dev/null                fi            fi            rm -rf "$STATE_SRC"            ;;    esacfi echo "Found $PATCH_COUNT patch(es) in $PATCHES_DIR"echo "Rust std source: $STD_SRC"echo "" FAILED=0CHECK_SRC="" cleanup() {    if [ -n "$CHECK_SRC" ] && [ -d "$CHECK_SRC" ]; then        rm -rf "$CHECK_SRC"    fi} trap cleanup EXIT if [ "$MODE" = "check" ]; then    CHECK_SRC="$(mktemp -d "${TMPDIR:-/tmp}/agentos-std-patch-check.XXXXXX")"    cp -a "$STD_SRC/." "$CHECK_SRC/"     # Patches can depend on files or context introduced by earlier patches, so    # classify the source tree using the complete ordered series. Testing each    # patch independently against pristine Rust incorrectly rejects those valid    # dependencies. First try reversing a fully applied tree; if that fails,    # verify that the full series applies to the original copy. Anything else is    # a partial or malformed tree and remains a hard failure.    STATE_SRC="$(mktemp -d "${TMPDIR:-/tmp}/agentos-std-patch-state.XXXXXX")"    cp -a "$STD_SRC/." "$STATE_SRC/"    FULLY_PATCHED=1    while IFS= read -r PATCH; do        if patch --batch --dry-run -R $PATCH_FLAGS -d "$STATE_SRC" < "$PATCH" > /dev/null 2>&1; then            patch --batch -R $PATCH_FLAGS -d "$STATE_SRC" < "$PATCH" > /dev/null 2>&1        else            FULLY_PATCHED=0            break        fi    done < <(printf '%s\n' "$PATCH_FILES" | sort -r)     if [ "$FULLY_PATCHED" -eq 1 ]; then        rm -rf "$CHECK_SRC"        CHECK_SRC="$STATE_SRC"    else        rm -rf "$STATE_SRC"        STATE_SRC="$(mktemp -d "${TMPDIR:-/tmp}/agentos-std-patch-state.XXXXXX")"        cp -a "$STD_SRC/." "$STATE_SRC/"        PRISTINE=1        FAILED_PATCH_NAME=""        while IFS= read -r PATCH; do            if patch --batch --dry-run $PATCH_FLAGS -d "$STATE_SRC" < "$PATCH" > /dev/null 2>&1; then                patch --batch $PATCH_FLAGS -d "$STATE_SRC" < "$PATCH" > /dev/null 2>&1            else                PRISTINE=0                FAILED_PATCH_NAME="$(basename "$PATCH")"                break            fi        done < <(printf '%s\n' "$PATCH_FILES" | sort)        rm -rf "$STATE_SRC"        if [ "$PRISTINE" -ne 1 ]; then            echo "FAIL: Rust std source is partially patched or the patch series is malformed at $FAILED_PATCH_NAME."            exit 1        fi    fi     STD_SRC="$CHECK_SRC"    echo "Checking against reconstructed pristine std source: $STD_SRC"    echo ""fi for PATCH in $PATCH_FILES; do    PATCH_NAME="$(basename "$PATCH")"     case "$MODE" in        check)            echo -n "Checking $PATCH_NAME ... "            if patch --batch --dry-run $PATCH_FLAGS -d "$STD_SRC" < "$PATCH" > /dev/null 2>&1; then                patch --batch $PATCH_FLAGS -d "$STD_SRC" < "$PATCH" > /dev/null 2>&1                echo "OK"            else                echo "FAIL (does not apply in sequence)"                FAILED=1            fi            ;;        apply)            echo -n "Applying $PATCH_NAME ... "            # Use `--forward` (-N) for idempotency: it applies hunks that are not            # yet present and SKIPS hunks already applied (reversed) instead of            # applying them a second time. Without this, additive (insert-only)            # patches stay forward-applicable after they are applied — their            # anchor context is still present — and a naive forward apply inserts            # a duplicate copy, producing E0119 conflicting-implementation errors            # on a re-run. `--forward` makes a second `make wasm` a no-op.            if patch --batch --forward --dry-run $PATCH_FLAGS -d "$STD_SRC" < "$PATCH" > /dev/null 2>&1; then                patch --batch --forward $PATCH_FLAGS -d "$STD_SRC" < "$PATCH" > /dev/null 2>&1                echo "applied"            else                echo "already applied (skipping)"            fi            ;;        reverse)            echo -n "Reversing $PATCH_NAME ... "            if patch --batch -R $PATCH_FLAGS -d "$STD_SRC" < "$PATCH" > /dev/null 2>&1; then                echo "reversed"            else                echo "not applied (skipping)"            fi            ;;    esacdone # Install companion source files that a patch declares (e.g. `pub mod process;`)# but cannot reliably carry inline: a `diff`/`patch` cannot create a brand-new# file in the std source from a `/dev/null` hunk reliably across patch versions# (the hunk is silently skipped, leaving the declared module with no source file# and a `file not found for module` E0583 build error). Convention mirrors the# vendored-crate mechanism in patch-vendor.sh: `std-patches/copy.manifest` with lines# "<src-relative-to-PATCHES_DIR> <dest-relative-to-STD_SRC>". Example:# `std/os/wasi/process.rs library/std/src/os/wasi/process.rs` installs the public# wasi child-pipe fd traits that 0007-wasi-childpipe-fd.patch's `pub mod process;`# references. Without this the patched std fails to compile (missing module).MANIFEST="$PATCHES_DIR/copy.manifest"if [ -f "$MANIFEST" ]; then    while read -r SRC DEST; do        # Skip blank lines and comments.        case "$SRC" in ""|\#*) continue ;; esac        case "$MODE" in            apply)                if [ ! -f "$PATCHES_DIR/$SRC" ]; then                    echo "copy.manifest source missing: $SRC"                    FAILED=1                    continue                fi                mkdir -p "$(dirname "$STD_SRC/$DEST")"                cp "$PATCHES_DIR/$SRC" "$STD_SRC/$DEST"                echo "Installed companion: $SRC -> $DEST"                ;;            reverse)                rm -f "$STD_SRC/$DEST"                echo "Removed companion: $DEST"                ;;            check)                if [ ! -f "$PATCHES_DIR/$SRC" ]; then                    echo "copy.manifest source missing: $SRC"                    FAILED=1                fi                ;;        esac    done < "$MANIFEST"fi echo ""if [ "$FAILED" -ne 0 ]; then    echo "Some patches failed to apply. Check patch compatibility with current nightly."    exit 1else    case "$MODE" in        check)   echo "All patches verified." ;;        apply)   echo "All patches applied successfully." ;;        reverse) echo "All patches reversed." ;;    esacfi