#!/usr/bin/env bash # # check-lockfiles.sh — every committed Cargo.lock is current. # # A lockfile is stale when cargo would change it: a crate's version was bumped, # or a dependency was added, and the lock was not regenerated. This tree holds # many workspaces that depend on one another by path, so a version bump in one # leaves the locks of the others behind. Nothing notices until a job that # passes `--locked` fails, for a reason that has nothing to do with what the # job tests. # # For each directory that holds a tracked Cargo.lock this runs # # cargo update --workspace --locked # # which resolves the workspace against its lock and fails if the lock would # change. It reads the registry index and any git dependency; it downloads no # crate and builds nothing. # # check-lockfiles.sh every tracked Cargo.lock, less the list below # check-lockfiles.sh DIR ... exactly these directories # # Exit 0: every lock is current. Exit 1: at least one is stale, or could not be # checked. A lock that could not be checked (no network, say) fails the run too, # under its own heading, so nobody goes looking for a stale lock that is not # one. set -uo pipefail # Directories that hold a Cargo.lock cargo does not read. Each needs a reason, # and each must still exist: a stale entry fails the run. SKIP=( # A vendored crate, patched into jouledesk-rs. Cargo resolves it as part of # that workspace, against that workspace's lock; its own lock is the # upstream's, kept with the vendored source. "jouledesk/jouledesk-rs/vendor/libwebrtc" ) skipped() { local d; for d in "${SKIP[@]}"; do [ "$d" = "$1" ] && return 0; done; return 1; } if [ $# -gt 0 ]; then dirs=("$@") else cd "$(git rev-parse --show-toplevel)" || exit 1 for d in "${SKIP[@]}"; do if [ ! -f "$d/Cargo.lock" ]; then echo "check-lockfiles: the skip list names $d, which holds no Cargo.lock. Remove the entry." >&2 exit 1 fi done dirs=() while IFS= read -r l; do d=$(dirname "$l") skipped "$d" || dirs+=("$d") done < <(git ls-files | grep -E '(^|/)Cargo\.lock$') fi if [ ${#dirs[@]} -eq 0 ]; then echo "check-lockfiles: found no Cargo.lock to check." >&2 exit 1 fi stale=(); unchecked=() for d in "${dirs[@]}"; do out=$( (cd "$d" && cargo update --workspace --locked) 2>&1 ); rc=$? if [ $rc -eq 0 ]; then continue elif printf '%s' "$out" | grep -qE -- '--locked was passed|lock file .* (is missing|needs to be updated)'; then stale+=("$d") echo "STALE $d" printf '%s\n' "$out" | grep -E '^error|^Caused by|^ ' | head -6 | sed 's/^/ /' else unchecked+=("$d") echo "ERROR $d (cargo could not resolve it; this is not a verdict on the lock)" printf '%s\n' "$out" | tail -8 | sed 's/^/ /' fi done if [ ${#stale[@]} -gt 0 ]; then echo echo "Stale lockfiles. Regenerate each, and commit the result:" for d in "${stale[@]}"; do echo " (cd $d && cargo update --workspace)"; done fi if [ ${#unchecked[@]} -gt 0 ]; then echo echo "Could not be checked: ${unchecked[*]}" fi if [ ${#stale[@]} -gt 0 ] || [ ${#unchecked[@]} -gt 0 ]; then exit 1 fi echo "check-lockfiles: ${#dirs[@]} lockfiles, all current."