cce workspace root: member list, lockfile and dependency pins
git clone https://git.lucas.co/cce.git
bump-revs: re-resolve each repinned crate's committed Cargo.lock
The script repinned Cargo.toml only. Inside the workspace cargo never reads
a member's own lock, so every tracked lock with git deps had drifted (two
still resolved cce-ui as a path dependency) and each standalone build
rewrote it silently.
After editing the manifests, each changed crate that tracks its Cargo.lock
gets it re-resolved with `cargo metadata` in a copy outside the workspace
-- the minimal update a standalone build makes -- and committed with the
pin. A lock that does not resolve fails the run before any commit.
Tested in a scratch root against a clone of cce-fonts rewound to its stale
state: one commit with both files, the lock byte-identical to the one
refreshed by hand; and with an unresolvable dependency: exit 1, nothing
committed, no temp dir left.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CLAUDE.md | 5 ++--
bump-revs.sh | 77 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
2 files changed, 77 insertions(+), 5 deletions(-)
diff --git a/CLAUDE.md b/CLAUDE.md
index 6ded5e2..7fdfebc 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -14,8 +14,9 @@ the `[patch]` block), `Cargo.lock`, `.cargo/config.toml` and this file. Its
`.gitignore` excludes every subdirectory by glob, so no crate can be swallowed
as an embedded repo and adding a crate needs no change here. It also holds
`bump-revs.sh`: after pushing a shared crate, run it to repoint the git pins in
-the crates that depend on it, because a stale pin never fails a build here --
-only a standalone build elsewhere.
+the crates that depend on it (and re-resolve each one's committed Cargo.lock to
+match), because a stale pin never fails a build here -- only a standalone build
+elsewhere.
Three rules are repeated here, and only these three, because acting against any
of them before reading the guide does damage that is annoying to undo:
diff --git a/bump-revs.sh b/bump-revs.sh
index c473e0b..7094767 100755
--- a/bump-revs.sh
+++ b/bump-revs.sh
@@ -32,6 +32,18 @@
# A manifest that should have changed but did not fails the run. Silently
# skipping is what let 21 repos sit unpushed for a day; the same rule applies
# here.
+#
+# THE LOCKFILE MOVES WITH THE PIN. A crate that commits its Cargo.lock gets it
+# re-resolved against the new pin and committed alongside it. Inside the
+# workspace cargo never reads a member's own lock (the root lock and the
+# [patch] block win), so a stale one rots exactly as a stale pin does, and
+# surfaces only as a standalone build rewriting it silently. Until 2026-09-25
+# this script touched Cargo.toml only, and all ten tracked locks with git deps
+# had drifted -- two still resolving cce-ui as a PATH dependency. The refresh
+# is `cargo metadata` in a copy of the crate outside the workspace: a minimal
+# update, the one a standalone build would make, moving nothing from
+# crates.io that the new pin does not require. A crate whose lock does not
+# resolve fails the run before anything is committed.
set -eu
@@ -44,7 +56,7 @@ for arg in "$@"; do
case "$arg" in
--dry-run) DRY=1 ;;
--commit) COMMIT=1 ;;
- -h|--help) sed -n '2,34p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;;
+ -h|--help) sed -n '2,47p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;;
-*) echo "unknown option: $arg" >&2; exit 2 ;;
*) WANT="$WANT $arg" ;;
esac
@@ -167,6 +179,62 @@ done
CHANGED=$(printf '%s' "$CHANGED" | tr ' ' '\n' | sed '/^$/d' | sort -u)
COUNT=$(printf '%s' "$CHANGED" | grep -c . || true)
+# Whether a crate commits its Cargo.lock. An untracked (or absent) lock has
+# nothing published to go stale, so it is left to whoever builds there.
+lock_tracked() {
+ git -C "$ROOT/$1" ls-files --error-unmatch Cargo.lock >/dev/null 2>&1
+}
+
+# Re-resolve one crate's Cargo.lock against its edited manifest, the way a
+# standalone clone would: the committed tree plus the work tree's manifest and
+# lock, in a directory outside the workspace (inside it cargo would find the
+# root and ignore this lock entirely).
+WORK=""
+refresh_lock() {
+ crate=$1
+ [ -n "$WORK" ] || { WORK=$(mktemp -d); trap 'rm -rf "$WORK"' EXIT; }
+ case "$WORK" in "$ROOT"/*)
+ say "!! temp dir $WORK is inside the workspace -- set TMPDIR elsewhere"; return 1 ;;
+ esac
+ copy="$WORK/$crate"
+ rm -rf "$copy" && mkdir -p "$copy"
+ git -C "$ROOT/$crate" archive HEAD | tar -x -C "$copy"
+ cp "$ROOT/$crate/Cargo.toml" "$ROOT/$crate/Cargo.lock" "$copy/"
+ if ! ( cd "$copy" && cargo metadata --quiet --format-version 1 >/dev/null 2>"$WORK/$crate.err" ); then
+ say "!! $crate: Cargo.lock does not resolve standalone:"
+ tail -n 5 "$WORK/$crate.err" | sed 's/^/ /'
+ return 1
+ fi
+ if cmp -s "$copy/Cargo.lock" "$ROOT/$crate/Cargo.lock"; then
+ say " $crate (already current)"
+ else
+ cp "$copy/Cargo.lock" "$ROOT/$crate/Cargo.lock"
+ say " $crate"
+ fi
+}
+
+if [ "$COUNT" != 0 ]; then
+ say ""
+ if [ -n "$DRY" ]; then
+ for crate in $CHANGED; do
+ lock_tracked "$crate" && say " would refresh: $crate/Cargo.lock"
+ done
+ else
+ say "refreshing lockfiles:"
+ LOCK_FAILED=""
+ for crate in $CHANGED; do
+ lock_tracked "$crate" || continue
+ refresh_lock "$crate" || LOCK_FAILED="$LOCK_FAILED $crate"
+ done
+ if [ -n "$LOCK_FAILED" ]; then
+ say ""
+ say "lock refresh failed:$LOCK_FAILED -- nothing committed; the"
+ say "manifests are edited in place, so fix the resolve and re-run"
+ exit 1
+ fi
+ fi
+fi
+
say ""
if [ "$COUNT" = 0 ]; then
say "every pin already current ($UNCHANGED) -- nothing to do"
@@ -182,11 +250,14 @@ if [ -n "$COMMIT" ] && [ -z "$DRY" ]; then
say ""
say "committing:"
for crate in $CHANGED; do
- ( cd "$ROOT/$crate" && git add Cargo.toml && git commit --quiet -m "Repin workspace dependencies to their published revs
+ files="Cargo.toml"
+ lock_tracked "$crate" && files="$files Cargo.lock"
+ ( cd "$ROOT/$crate" && git add $files && git commit --quiet -m "Repin workspace dependencies to their published revs
The pinned revs only affect builds outside this workspace, where an app is
cloned on its own, so a stale pin never fails a build here. Bumped by
-bump-revs.sh after the dependency was pushed." ) && say " $crate"
+bump-revs.sh after the dependency was pushed; Cargo.lock (when committed)
+is re-resolved to match." ) && say " $crate"
done
say ""
say "committed -- each crate's post-commit hook pushes it to origin (GitHub);"