From 5274527b2ade74b8ae68d4a0fede01e8ddb9655a Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Thu, 11 Jun 2026 13:58:40 +0200 Subject: [PATCH] fix(ci): refresh Foundry locks for patched crates --- scripts/foundry-patch.sh | 166 ++++++++++++++++++++++++++++++++------- 1 file changed, 138 insertions(+), 28 deletions(-) diff --git a/scripts/foundry-patch.sh b/scripts/foundry-patch.sh index 07adcc81ce..33d1da93cb 100755 --- a/scripts/foundry-patch.sh +++ b/scripts/foundry-patch.sh @@ -1,8 +1,8 @@ #!/usr/bin/env bash # # Patches a Foundry checkout to resolve tempo-* crates from a local Tempo -# checkout instead of git/crates-io. Used by both GitHub Actions (specs.yml) -# and the Argo invariant-tests workflow. +# checkout and to reuse Tempo's crates.io overrides for shared upstream crates. +# Used by both GitHub Actions (specs.yml) and the Argo invariant-tests workflow. # # Usage: # scripts/foundry-patch.sh @@ -36,6 +36,51 @@ if grep -q '^\[patch\."https://github.com/tempoxyz/tempo"\]' "$FOUNDRY_CARGO"; t exit 0 fi +upsert_crates_io_patch() { + local crate="$1" + local replacement="$2" + local tmp_cargo + + tmp_cargo="$(mktemp "${FOUNDRY_CARGO}.XXXXXX")" + awk -v crate="$crate" -v replacement="$replacement" ' + /^\[patch\.crates-io\]/ { + seen = 1 + in_section = 1 + print + next + } + in_section && /^\[/ { + if (!done) { + print replacement + done = 1 + } + in_section = 0 + } + in_section { + line = $0 + sub(/^[[:space:]]+/, "", line) + if (index(line, crate " = ") == 1) { + if (!done) { + print replacement + done = 1 + } + next + } + } + { print } + END { + if (!seen) { + print "" + print "[patch.crates-io]" + print replacement + } else if (in_section && !done) { + print replacement + } + } + ' "$FOUNDRY_CARGO" > "$tmp_cargo" + mv "$tmp_cargo" "$FOUNDRY_CARGO" +} + # ── 1. Discover tempo-* workspace crates that have local paths ────────────── PATCHES="$({ awk ' @@ -70,41 +115,50 @@ while IFS=$'\t' read -r crate path; do [[ -n "$crate" ]] || continue local_path="${TEMPO_ROOT}/${path}" replacement="${crate} = { path = \"${local_path}\" }" - tmp_cargo="$(mktemp "${FOUNDRY_CARGO}.XXXXXX")" - awk -v crate="$crate" -v replacement="$replacement" ' + upsert_crates_io_patch "$crate" "$replacement" +done <<< "$PATCHES" + +# Tempo PRs sometimes patch shared upstream crates while Foundry still carries +# a stale lockfile entry for the registry version. Mirror those overrides into +# Foundry before resolving its lockfile so Forge builds against the same graph +# as the checked-out Tempo branch. +TEMPO_CRATES_IO_PATCHES="$( + awk ' /^\[patch\.crates-io\]/ { - seen = 1 in_section = 1 - print next } in_section && /^\[/ { - if (!done) { - print replacement - done = 1 - } - in_section = 0 + exit } - in_section && index($0, crate " = ") == 1 { - if (!done) { - print replacement - done = 1 + in_section { + line = $0 + sub(/^[[:space:]]+/, "", line) + sub(/[[:space:]]+$/, "", line) + if (line == "" || line ~ /^#/) { + next } - next - } - { print } - END { - if (!seen) { - print "" - print "[patch.crates-io]" - print replacement - } else if (in_section && !done) { - print replacement + split(line, parts, "=") + crate = parts[1] + gsub(/[[:space:]]/, "", crate) + if (crate == "" || crate ~ /^tempo-/ || line !~ /^[A-Za-z0-9_-]+[[:space:]]*=/) { + next } + print crate "\t" line } - ' "$FOUNDRY_CARGO" > "$tmp_cargo" - mv "$tmp_cargo" "$FOUNDRY_CARGO" -done <<< "$PATCHES" + ' "$TEMPO_CARGO" | sort -u +)" + +IMPORTED_CRATES_IO_PATCH_NAMES=() +if [[ -n "$TEMPO_CRATES_IO_PATCHES" ]]; then + echo "Importing Tempo [patch.crates-io] overrides into Foundry:" + while IFS=$'\t' read -r crate replacement; do + [[ -n "$crate" ]] || continue + echo "$replacement" + upsert_crates_io_patch "$crate" "$replacement" + IMPORTED_CRATES_IO_PATCH_NAMES+=("$crate") + done <<< "$TEMPO_CRATES_IO_PATCHES" +fi echo "Updated Cargo.toml patch sections:" sed -n '/^\[patch\./,$p' "$FOUNDRY_CARGO" @@ -156,6 +210,61 @@ update_stale_tempo_git_packages() { cargo update "${update_args[@]}" >/dev/null } +update_imported_crates_io_patches() { + if ((${#IMPORTED_CRATES_IO_PATCH_NAMES[@]} == 0)); then + return 0 + fi + + local patch_pkgs="" + local crate + for crate in "${IMPORTED_CRATES_IO_PATCH_NAMES[@]}"; do + while IFS= read -r pkg; do + [[ -n "$pkg" ]] || continue + patch_pkgs+="${pkg}"$'\n' + done < <( + awk -v crate="$crate" ' + /^\[\[package\]\]/ { + if (name == crate && version != "") { + print name "@" version + } + name = "" + version = "" + next + } + /^name = / { + name = $3 + gsub(/"/, "", name) + next + } + /^version = / { + version = $3 + gsub(/"/, "", version) + next + } + END { + if (name == crate && version != "") { + print name "@" version + } + } + ' Cargo.lock + ) + done + + patch_pkgs="$(printf '%s' "$patch_pkgs" | sort -u)" + if [[ -z "$patch_pkgs" ]]; then + return 0 + fi + + local update_args=() + while IFS= read -r pkg; do + [[ -n "$pkg" ]] || continue + update_args+=("-p" "$pkg") + done <<< "$patch_pkgs" + + echo "Cargo.lock contains Tempo [patch.crates-io] packages; running 'cargo update ${update_args[*]}'" + cargo update "${update_args[@]}" >/dev/null +} + # ── 4. Re-resolve the lockfile without upgrading unrelated crates ────────── # `cargo update` can pull newer upstream deps from Foundry's workspace, which is non-deterministic. # A normal resolver pass is enough to rewrite the lockfile entries for the tempo path overrides. @@ -173,6 +282,7 @@ update_stale_tempo_git_packages() { # conflicts twice in a row (i.e. `cargo update` made no progress). pushd "$FOUNDRY_ROOT" >/dev/null update_stale_tempo_git_packages +update_imported_crates_io_patches prev_conflict_pkg="" while true; do err="$(cargo metadata --format-version=1 --no-default-features 2>&1 >/dev/null)" && break