diff --git a/.changelog/_unreleased.toml b/.changelog/_unreleased.toml index 28ec3dba..94365f7b 100644 --- a/.changelog/_unreleased.toml +++ b/.changelog/_unreleased.toml @@ -2,6 +2,7 @@ id = "b6fb5e00-0bed-4e07-8cf3-c16c394d40fe" type = "improvement" description = "Replace `rich` logger with `loguru` which produces nicer log formatting and requires less setup" +author = "@NiklasRosenstein" [[entries]] id = "5f259115-ab4d-43d1-b23f-6b2436f26bb2" @@ -9,3 +10,10 @@ type = "feature" description = "Add `buildscript(interpreter_constraint)` argument" author = "@NiklasRosenstein" component = "kraken-wrapper" + +[[entries]] +id = "9a319506-3900-402f-a40b-ad9052acfbbf" +type = "fix" +description = "Detect Pyenv shims in interpreter constraint matching and ignore them by default; and if not ignore, then do not commit their version to the cache as the Python version they represent might change." +author = "@NiklasRosenstein" +component = "kraken-wrapper" diff --git a/kraken-build/src/kraken/common/findpython.py b/kraken-build/src/kraken/common/findpython.py index 1c0b1b13..5be1ce90 100644 --- a/kraken-build/src/kraken/common/findpython.py +++ b/kraken-build/src/kraken/common/findpython.py @@ -22,6 +22,7 @@ class InterpreterCandidate(TypedDict): path: str min_version: NotRequired[str | None] exact_version: NotRequired[str | None] + shim: NotRequired[bool] class Interpreter(TypedDict): @@ -94,6 +95,17 @@ def get_candidates( installed via pyenv are also included in the results. """ + for candidate in _get_candidates(): + if ".pyenv/shims" in candidate["path"].replace("\\", "/"): + candidate["shim"] = True + yield candidate + + +def _get_candidates( + path_list: Sequence[str | Path] | None = None, check_pyenv: bool = True +) -> Iterator[InterpreterCandidate]: + """Internal. Implementation of `get_candidates()` without Pyenv shim detection.""" + if path_list is None: path_list = os.environ["PATH"].split(os.pathsep) @@ -178,7 +190,9 @@ def get_python_interpreter_version(python_bin: str) -> str: def evaluate_candidates( - candidates: Iterable[InterpreterCandidate], cache: InterpreterVersionCache | None = None + candidates: Iterable[InterpreterCandidate], + cache: InterpreterVersionCache | None = None, + ignore_shims: bool = True, ) -> list[Interpreter]: """ Evaluates Python interpreter candidates and returns the deduplicated list of interpreters that were found. @@ -188,6 +202,9 @@ def evaluate_candidates( visited: set[Path] = set() for choice in candidates: + if ignore_shims and choice.get("shim"): + continue + try: path = Path(choice["path"]).resolve() except FileNotFoundError: @@ -198,14 +215,14 @@ def evaluate_candidates( continue visited.add(path) - version = cache.get_version(path) if cache else None + version = cache.get_version(path) if cache and not choice.get("shim") else None if version is None: try: version = get_python_interpreter_version(str(path)) except (subprocess.CalledProcessError, RuntimeError, FileNotFoundError): logger.debug("Failed to get version for Python interpreter %s", path, exc_info=True) continue - if cache: + if cache and not choice.get("shim"): cache.set_version(path, version) interpreter: Interpreter = {"path": str(path), "version": version}