The blow-the-venv-on-update contract needs rebuilds to be cheap, and sealed installs need a writable venv at all (settled 2026-09-02 plan, task 7): - uv_cache_dir(): hermes-owned machine cache at <default hermes root>/cache/uv — content-addressed, shared across profiles. uv_env() ALWAYS pins UV_CACHE_DIR there (ambient UV_* stripped), so the cache that ships is the cache that gets used. First call on a sealed install seeds it from the payload's shipped uv-cache/ (read-only payload can't serve uv's working cache); the .seeded marker makes it once-only and non-clobbering. - pm bundle stages the warmed cache into the payload after the venv sync (uv-cache/ beside manifest.json) — warm 'uv sync --offline' rebuilds probed at 0.4s vs 1.2s cold. - Venv.venv_dir(): sealed installs resolve the MUTABLE venv to the machine hermes root (<root>/venv), not the read-only payload; dev/source installs keep the repo-local venv unchanged. - Venv.seed_mutable_venv(): the bootstrap seed — lazy-off installs copy the payload's shipped venv out as the starting point; lazy-on installs skip the copy (first sync builds fresh from the shipped cache). adopt() triggers it (KeyError-guarded, failure reported never fatal — a cold sync still converges). tests/pm/test_uv_cache.py: 6 tests (env pinning + ambient strip, payload seed + marker once-only, cold machine, sealed venv_dir, seed copy idempotence, lazy-on skip). tests/pm: 175 passed, 0 failed.
129 lines
4.4 KiB
Python
129 lines
4.4 KiB
Python
"""pm uv cache: hermes-owned UV_CACHE_DIR + sealed-venv bootstrap seed.
|
|
|
|
The cache ships with bundles and is copied out to the writable machine
|
|
cache on first use; every pm-internal uv invocation pins UV_CACHE_DIR.
|
|
The mutable venv on sealed installs lives in the machine hermes root,
|
|
seeded from the payload's shipped venv when lazy installs are off.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
import pm.packages as pkgs
|
|
|
|
|
|
def test_uv_env_pins_cache_dir(monkeypatch, tmp_path):
|
|
monkeypatch.setattr(pkgs, "uv_cache_dir", lambda: tmp_path / "c")
|
|
env = pkgs.uv_env({"UV_CACHE_DIR": "/ambient/user/cache", "PATH": "x"})
|
|
assert env["UV_CACHE_DIR"] == str(tmp_path / "c")
|
|
# ambient UV_ vars are stripped, not inherited
|
|
assert "UV_PROJECT_ENVIRONMENT" not in env
|
|
|
|
|
|
def test_uv_cache_dir_seeds_from_payload(monkeypatch, tmp_path):
|
|
home = tmp_path / "home"
|
|
home.mkdir()
|
|
payload = tmp_path / "payload"
|
|
(payload / "uv-cache" / "wheels-v5").mkdir(parents=True)
|
|
(payload / "uv-cache" / "wheels-v5" / "some.pkg").write_text("x", encoding="utf-8")
|
|
|
|
import hermes_constants
|
|
|
|
monkeypatch.setattr(hermes_constants, "get_default_hermes_root", lambda: home)
|
|
import pm.paths as paths_mod
|
|
|
|
monkeypatch.setattr(paths_mod, "store_root", lambda: payload / "tools")
|
|
|
|
machine = pkgs.uv_cache_dir()
|
|
assert machine == home / "cache" / "uv"
|
|
# seed copied out
|
|
assert (machine / "wheels-v5" / "some.pkg").read_text(encoding="utf-8") == "x"
|
|
# seeded marker written → second call doesn't re-copy
|
|
assert (machine / ".seeded").is_file()
|
|
(payload / "uv-cache" / "wheels-v5" / "some.pkg").write_text("changed", encoding="utf-8")
|
|
pkgs.uv_cache_dir()
|
|
assert (machine / "wheels-v5" / "some.pkg").read_text(encoding="utf-8") == "x"
|
|
|
|
|
|
def test_uv_cache_dir_cold_machine_no_payload(monkeypatch, tmp_path):
|
|
home = tmp_path / "home"
|
|
home.mkdir()
|
|
import hermes_constants
|
|
|
|
monkeypatch.setattr(hermes_constants, "get_default_hermes_root", lambda: home)
|
|
import pm.paths as paths_mod
|
|
|
|
monkeypatch.setattr(paths_mod, "store_root", lambda: tmp_path / "nowhere" / "tools")
|
|
|
|
machine = pkgs.uv_cache_dir()
|
|
assert machine == home / "cache" / "uv"
|
|
assert (machine / ".seeded").is_file()
|
|
|
|
|
|
def test_venv_dir_sealed_goes_to_machine_root(monkeypatch, tmp_path):
|
|
home = tmp_path / "home"
|
|
home.mkdir()
|
|
import sys
|
|
|
|
import hermes_constants
|
|
|
|
monkeypatch.setattr(hermes_constants, "get_default_hermes_root", lambda: home)
|
|
ensure_mod = sys.modules["pm.ensure"]
|
|
monkeypatch.setattr(ensure_mod, "sealed", lambda: True)
|
|
venv = pkgs.Venv()
|
|
assert venv.venv_dir() == home / "venv"
|
|
|
|
|
|
def test_seed_mutable_venv_copies_payload_venv(monkeypatch, tmp_path):
|
|
home = tmp_path / "home"
|
|
home.mkdir()
|
|
payload = tmp_path / "payload"
|
|
(payload / "venv" / "Scripts").mkdir(parents=True)
|
|
(payload / "venv" / "Scripts" / "python.exe").write_text("bin", encoding="utf-8")
|
|
|
|
import hermes_constants
|
|
|
|
monkeypatch.setattr(hermes_constants, "get_default_hermes_root", lambda: home)
|
|
import sys
|
|
|
|
ensure_mod = sys.modules["pm.ensure"]
|
|
import pm.paths as paths_mod
|
|
|
|
monkeypatch.setattr(ensure_mod, "sealed", lambda: True)
|
|
monkeypatch.setattr(ensure_mod, "lazy_installs_allowed", lambda: False)
|
|
monkeypatch.setattr(paths_mod, "store_root", lambda: payload / "tools")
|
|
|
|
venv = pkgs.Venv()
|
|
reason = venv.seed_mutable_venv()
|
|
assert reason is None
|
|
seeded = home / "venv"
|
|
assert (seeded / "Scripts" / "python.exe").read_text(encoding="utf-8") == "bin"
|
|
# idempotent: second call is a no-op
|
|
assert venv.seed_mutable_venv() is None
|
|
|
|
|
|
def test_seed_mutable_venv_lazy_on_skips_copy(monkeypatch, tmp_path):
|
|
home = tmp_path / "home"
|
|
home.mkdir()
|
|
payload = tmp_path / "payload"
|
|
(payload / "venv").mkdir(parents=True)
|
|
|
|
import hermes_constants
|
|
|
|
monkeypatch.setattr(hermes_constants, "get_default_hermes_root", lambda: home)
|
|
import sys
|
|
|
|
ensure_mod = sys.modules["pm.ensure"]
|
|
import pm.paths as paths_mod
|
|
|
|
monkeypatch.setattr(ensure_mod, "sealed", lambda: True)
|
|
monkeypatch.setattr(ensure_mod, "lazy_installs_allowed", lambda: True)
|
|
monkeypatch.setattr(paths_mod, "store_root", lambda: payload / "tools")
|
|
|
|
venv = pkgs.Venv()
|
|
assert venv.seed_mutable_venv() is None
|
|
assert not (home / "venv").exists() # builds fresh later, no seed copy
|