fix(pm): re-seed the uv cache when an update ships a new lock
uv_cache_dir() wrote a bare ".seeded" marker after the first seed and never compared against the payload again, so an updated install never received the new payload's wheels. Enabling a plugin re-resolves without --offline, so uv fetched the new pins from PyPI instead; for cryptography 50.0.1 on Windows ARM64, which has no wheel on PyPI, that is an sdist Rust build. The marker now records the running uv.lock's digest, so the first call under a new lock merges the new payload into the existing cache.
This commit is contained in:
@@ -320,24 +320,33 @@ def uv_cache_dir() -> Path:
|
||||
"""The hermes-owned uv cache: machine-scoped and shared (keyed by
|
||||
content — two profiles reuse one cache), anchored to the DEFAULT
|
||||
hermes root like partials_root(). A bundle ships a seeded copy at
|
||||
the payload root (uv-cache/); the first call on a sealed install
|
||||
copies it out to the writable machine cache (the read-only payload
|
||||
the payload root (uv-cache/); the first call under each uv.lock
|
||||
merges it into the writable machine cache (the read-only payload
|
||||
can't serve uv's working cache), and a warm `uv sync --offline`
|
||||
from it is near-free (probed: 0.4s vs 1.2s cold) — the blow-away-
|
||||
on-update contract depends on it. uv's default cache location is
|
||||
per-user/platform-opinionated and never used by pm."""
|
||||
from hermes_constants import get_default_hermes_root
|
||||
|
||||
from pm.paths import repo_root, store_root
|
||||
|
||||
machine_cache = get_default_hermes_root() / "cache" / "uv"
|
||||
marker = machine_cache / ".seeded"
|
||||
if not marker.is_file():
|
||||
# The marker names the lock whose payload was merged. An update ships a new payload cache for
|
||||
# a new lock; a bare "done" marker skipped it forever, so plugin rebuilds fetched the new pins
|
||||
# from the network (and built sdists where the index has no wheel for the target).
|
||||
lock = repo_root() / "uv.lock"
|
||||
seed = _uv_lock_digest(lock).hex() if lock.is_file() else "1"
|
||||
try:
|
||||
current = marker.read_text(encoding="utf-8-sig").strip() == seed
|
||||
except OSError:
|
||||
current = False
|
||||
if not current:
|
||||
# Seed from a shipped bundle cache when present (payload root =
|
||||
# store_root().parent on a sealed install). Record completion ONLY after a clean copy: a
|
||||
# partial seed that marked itself done would never be retried, and every later offline
|
||||
# sync that needs the missing entries fails closed.
|
||||
try:
|
||||
from pm.paths import store_root
|
||||
|
||||
# uv's cache trees run deep; both roots get the long spelling so
|
||||
# the copy is not cut at MAX_PATH. The returned path stays ordinary.
|
||||
payload_cache = long_root(store_root().parent / "uv-cache")
|
||||
@@ -359,7 +368,7 @@ def uv_cache_dir() -> Path:
|
||||
else:
|
||||
try:
|
||||
marker.parent.mkdir(parents=True, exist_ok=True)
|
||||
marker.write_text("1", encoding="utf-8")
|
||||
marker.write_text(seed, encoding="utf-8")
|
||||
except OSError:
|
||||
pass
|
||||
return machine_cache
|
||||
|
||||
@@ -30,6 +30,25 @@ def payload_cache(tmp_path, monkeypatch):
|
||||
return get_default_hermes_root() / "cache" / "uv"
|
||||
|
||||
|
||||
def test_update_merges_the_new_payload_into_an_existing_seed(payload_cache, tmp_path, monkeypatch):
|
||||
"""An update ships a payload cache for a new lock; its wheels must reach the machine cache.
|
||||
|
||||
Regression: ``.seeded`` meant "done forever", so an updated install never received the new
|
||||
pins' wheels and plugin rebuilds fetched them from the index.
|
||||
"""
|
||||
code = tmp_path / "code"
|
||||
code.mkdir()
|
||||
(code / "uv.lock").write_text("version = 1 # old\n", encoding="utf-8")
|
||||
monkeypatch.setattr(paths, "repo_root", lambda: code)
|
||||
packages.uv_cache_dir()
|
||||
|
||||
added = paths.store_root().parent / "uv-cache" / "archive-v0" / "bucket" / "new.py"
|
||||
added.write_text("new pin\n", encoding="utf-8")
|
||||
(code / "uv.lock").write_text("version = 1 # new\n", encoding="utf-8")
|
||||
packages.uv_cache_dir()
|
||||
assert (payload_cache / "archive-v0" / "bucket" / "new.py").read_text(encoding="utf-8-sig") == "new pin\n"
|
||||
|
||||
|
||||
def test_partial_seed_is_retried_on_the_next_install(payload_cache, monkeypatch):
|
||||
real_copytree = shutil.copytree
|
||||
attempts: list[int] = []
|
||||
|
||||
Reference in New Issue
Block a user