fix(install): scrap rebuild venv

This commit is contained in:
ethernet
2026-06-04 23:20:29 -04:00
parent 96cd37e212
commit fb853a1783
7 changed files with 23 additions and 433 deletions
+6 -73
View File
@@ -8029,20 +8029,12 @@ def _update_via_zip(args):
# individually so update does not silently strip working capabilities.
print("→ Updating Python dependencies...")
from hermes_cli.managed_uv import ensure_uv, rebuild_venv, update_managed_uv
from hermes_cli.managed_uv import ensure_uv, update_managed_uv
# Keep managed uv current — runs `uv self update` if we already have one.
update_managed_uv()
uv_bin, fresh_bootstrap = ensure_uv()
# First-time managed uv install on an existing checkout: the old venv
# may point to a Python without FTS5. Rebuild it so the new managed
# uv provides a fresh interpreter with FTS5 guaranteed.
if fresh_bootstrap and uv_bin:
if not rebuild_venv(uv_bin, PROJECT_ROOT / "venv"):
raise RuntimeError(
"venv rebuild failed; aborting update before dependency install"
)
uv_bin = ensure_uv()
pip_cmd = [sys.executable, "-m", "pip"]
if not uv_bin:
@@ -8697,48 +8689,6 @@ def _venv_scripts_dir() -> Path | None:
return scripts if scripts.is_dir() else None
def _wait_for_interpreter_venv_ready(*, timeout: float = 15.0) -> bool:
"""Ensure the venv hosting ``sys.executable`` has an intact ``pyvenv.cfg``.
During ``hermes update`` the managed-uv path can rebuild the project venv
(``rebuild_venv`` ``shutil.rmtree`` + ``uv venv``) before the
desktop-rebuild and profile-skills-sync steps run. Both of those steps
spawn a child process with ``sys.executable``. If they fire while the venv
is mid-rewrite, the interpreter launcher finds the venv directory but no
``pyvenv.cfg`` yet and aborts with the bare stderr line
``No pyvenv.cfg file`` surfacing as a spurious "Desktop build failed" /
"sync failed" on an update that otherwise succeeded.
A venv's ``pyvenv.cfg`` sits one level up from the interpreter's ``bin`` /
``Scripts`` dir. If ``sys.executable`` is NOT a venv interpreter (no
sibling marker dir, e.g. a system Python on PATH), there is nothing to
wait for and we return True immediately. Otherwise we poll briefly for the
marker to (re)appear the rewrite window is short and return whether
it's present. Best-effort: never raises, callers proceed regardless.
"""
try:
exe = Path(sys.executable).resolve()
except Exception:
return True
venv_dir = exe.parent.parent # .../venv/{bin,Scripts}/python -> .../venv
bin_dir = venv_dir / ("Scripts" if _is_windows() else "bin")
if not bin_dir.is_dir():
# Not a venv-hosted interpreter — pyvenv.cfg is irrelevant.
return True
cfg = venv_dir / "pyvenv.cfg"
if cfg.is_file():
return True
deadline = _time.monotonic() + max(0.0, timeout)
while _time.monotonic() < deadline:
if cfg.is_file():
return True
_time.sleep(0.25)
return cfg.is_file()
def _hermes_exe_shims(scripts_dir: Path) -> list[Path]:
"""Entry-point shims that uv may try to rewrite during ``pip install -e .``.
@@ -10133,7 +10083,7 @@ def _cmd_update_pip(args):
# Keep managed uv current before using it.
update_managed_uv()
uv, _fresh_bootstrap = ensure_uv()
uv = ensure_uv()
in_venv = sys.prefix != sys.base_prefix
# pipx-managed installs live under .../pipx/venvs/<name>/...
pipx_managed = "pipx" in sys.prefix.split(os.sep)
@@ -10575,20 +10525,12 @@ def _cmd_update_impl(args, gateway_mode: bool):
# breaks on this machine, keep base deps and reinstall the remaining extras
# individually so update does not silently strip working capabilities.
print("→ Updating Python dependencies...")
from hermes_cli.managed_uv import ensure_uv, rebuild_venv, update_managed_uv
from hermes_cli.managed_uv import ensure_uv, update_managed_uv
# Keep managed uv current — runs `uv self update` if we already have one.
update_managed_uv()
uv_bin, fresh_bootstrap = ensure_uv()
# First-time managed uv install on an existing checkout: the old venv
# may point to a Python without FTS5. Rebuild it so the new managed
# uv provides a fresh interpreter with FTS5 guaranteed.
if fresh_bootstrap and uv_bin:
if not rebuild_venv(uv_bin, PROJECT_ROOT / "venv"):
raise RuntimeError(
"venv rebuild failed; aborting update before dependency install"
)
uv_bin = ensure_uv()
pip_cmd = [sys.executable, "-m", "pip"]
if not uv_bin:
@@ -10651,18 +10593,13 @@ def _cmd_update_impl(args, gateway_mode: bool):
has_desktop_app = _desktop_packaged_executable(desktop_dir) is not None or _desktop_dist_exists(desktop_dir)
if (desktop_dir / "package.json").exists() and shutil.which("npm") and has_desktop_app:
print("→ Checking if desktop app needs rebuilding...")
# The Python-dependency step above may have rebuilt the venv that
# hosts sys.executable. Wait for its pyvenv.cfg to settle before
# spawning, or the child interpreter aborts with "No pyvenv.cfg
# file" and the rebuild spuriously "fails" on a successful update.
_wait_for_interpreter_venv_ready()
_desktop_build_cmd = [sys.executable, "-m", "hermes_cli.main", "desktop", "--build-only"]
# Stream the build output live (long Electron builds otherwise
# look hung). On the rare nonzero exit, retry once after waiting
# again for the venv — this covers a still-settling rebuild window
# the first wait didn't fully catch.
build_result = subprocess.run(_desktop_build_cmd, cwd=PROJECT_ROOT, check=False)
if build_result.returncode != 0 and _wait_for_interpreter_venv_ready():
if build_result.returncode != 0:
build_result = subprocess.run(_desktop_build_cmd, cwd=PROJECT_ROOT, check=False)
if build_result.returncode != 0:
print(" ⚠ Desktop build failed (non-fatal; run `hermes desktop` to retry)")
@@ -10719,10 +10656,6 @@ def _cmd_update_impl(args, gateway_mode: bool):
if all_profiles:
print()
print("→ Syncing bundled skills to all profiles...")
# seed_profile_skills spawns sys.executable; if the venv was
# just rebuilt above, wait for pyvenv.cfg before the loop so
# the children don't abort with "No pyvenv.cfg file".
_wait_for_interpreter_venv_ready()
for p in all_profiles:
try:
r = seed_profile_skills(p.path, quiet=True)