fix(tools): ignore the running venv when detecting a path-tool on PATH
_on_path() led with a bare shutil.which(), which resolves against the calling process's PATH. When castle-api runs inside the repo venv (uv run), a tool merely colocated there — e.g. a stray editable a dev uv-pip-installed — read as "installed on PATH", so the UI showed installed and the converge planner reported no drift. Strip the running interpreter's own venv bin from the search path; tools installed anywhere else on PATH still count.
This commit is contained in:
@@ -11,8 +11,10 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
|
import sys
|
||||||
import time
|
import time
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
@@ -68,14 +70,38 @@ def _uv_tool_packages() -> set[str]:
|
|||||||
return pkgs
|
return pkgs
|
||||||
|
|
||||||
|
|
||||||
def _on_path(name: str) -> bool:
|
def _own_venv_bins() -> set[str]:
|
||||||
"""Whether a tool is installed, PATH-independent and script-name-independent.
|
"""Bin dirs of the *running* interpreter's virtualenv.
|
||||||
|
|
||||||
Checks, in order: the console script on PATH, the script in ~/.local/bin
|
Excluded from install detection: when the api server (or a dev `castle`) runs
|
||||||
(uv's install dir), and finally `uv tool list` by *package* name — the last
|
inside a project venv via `uv run`, a tool merely *colocated* there — e.g. an
|
||||||
catches tools whose executable is named differently from the program.
|
editable a dev accidentally `uv pip install`ed while that venv was active — must
|
||||||
|
not read as "installed on PATH" for the user. We only strip *our own* venv, not
|
||||||
|
every venv, so tools legitimately installed elsewhere on PATH still count.
|
||||||
"""
|
"""
|
||||||
if shutil.which(name) is not None:
|
bins = {os.path.normpath(os.path.dirname(sys.executable))} # the interpreter's own bin
|
||||||
|
bins.add(os.path.normpath(os.path.join(sys.prefix, "bin"))) # venv root → bin
|
||||||
|
venv = os.environ.get("VIRTUAL_ENV")
|
||||||
|
if venv:
|
||||||
|
bins.add(os.path.normpath(os.path.join(venv, "bin")))
|
||||||
|
return bins
|
||||||
|
|
||||||
|
|
||||||
|
def _on_path(name: str) -> bool:
|
||||||
|
"""Whether a tool is installed, script-name-independent and blind to our own venv.
|
||||||
|
|
||||||
|
Checks, in order: the console script on PATH (minus the running interpreter's
|
||||||
|
own venv bin — see :func:`_own_venv_bins`), the script in ~/.local/bin (uv's
|
||||||
|
install dir), and finally `uv tool list` by *package* name — the last catches
|
||||||
|
tools whose executable is named differently from the program.
|
||||||
|
"""
|
||||||
|
exclude = _own_venv_bins()
|
||||||
|
search = os.pathsep.join(
|
||||||
|
p
|
||||||
|
for p in (os.environ.get("PATH") or os.defpath).split(os.pathsep)
|
||||||
|
if p and os.path.normpath(p) not in exclude
|
||||||
|
)
|
||||||
|
if shutil.which(name, path=search) is not None:
|
||||||
return True
|
return True
|
||||||
if (Path.home() / ".local" / "bin" / name).exists():
|
if (Path.home() / ".local" / "bin" / name).exists():
|
||||||
return True
|
return True
|
||||||
|
|||||||
Reference in New Issue
Block a user