diff --git a/.changelog/_unreleased.toml b/.changelog/_unreleased.toml index e8e08ea8..08d8babf 100644 --- a/.changelog/_unreleased.toml +++ b/.changelog/_unreleased.toml @@ -4,6 +4,20 @@ type = "feature" description = "Make Rust builds respect Cargo.lock when present" author = "jon.gjengset@helsing.ai" +[[entries]] +id = "ebe30d77-1206-4437-8aa2-7373e9335926" +type = "improvement" +description = "Improve Windows support in `krakenw` by supporting the `PYENV` env-var and using the right `pyhon.exe` path on Windows" +author = "@NiklasRosenstein" +component = "kraken-wrapper" + +[[entries]] +id = "a8200c89-b05a-46ce-895c-c4a4f5a736a6" +type = "fix" +description = "Do not use `dmypy` on Windows" +author = "@NiklasRosenstein" +component = "kraken-build" + [[entries]] id = "db65bd12-a9d1-472c-a38b-6f889cdda683" type = "improvement" diff --git a/kraken-build/src/kraken/common/findpython.py b/kraken-build/src/kraken/common/findpython.py index e1ea3a65..1775f754 100644 --- a/kraken-build/src/kraken/common/findpython.py +++ b/kraken-build/src/kraken/common/findpython.py @@ -141,12 +141,25 @@ def get_candidates( if match: yield {"path": str(command), "exact_version": match.group(1)} - # pyenv - pyenv_versions = Path("~/.pyenv/versions").expanduser() - if check_pyenv and pyenv_versions.is_dir(): + # pyenv (+Windows) + if check_pyenv: + pyenv_versions = Path("~/.pyenv/versions").expanduser() + if not pyenv_versions.is_dir(): + pyenv = os.getenv("PYENV") + if pyenv: + pyenv_versions = Path(pyenv).expanduser().joinpath("versions") + elif os.name == "nt": + pyenv_versions = Path("~/.pyenv/pyenv-win/versions").expanduser() + else: + pyenv_versions = None + + if pyenv_versions and pyenv_versions.is_dir(): for item in pyenv_versions.iterdir(): if re.match(r"\d+\.\d+\.\d+$", item.name) and item.is_dir(): - yield {"path": str(item / "bin" / "python"), "exact_version": item.name} + if os.name == "nt": + yield {"path": str(item / "python.exe"), "exact_version": item.name} + else: + yield {"path": str(item / "bin" / "python"), "exact_version": item.name} yield {"path": sys.executable, "exact_version": ".".join(map(str, sys.version_info[:3]))} @@ -192,7 +205,7 @@ def evaluate_candidates( if version is None: try: version = get_python_interpreter_version(str(path)) - except (subprocess.CalledProcessError, RuntimeError): + except (subprocess.CalledProcessError, RuntimeError, FileNotFoundError): logger.debug("Failed to get version for Python interpreter %s", path, exc_info=True) continue if cache: diff --git a/kraken-build/src/kraken/std/python/tasks/base_task.py b/kraken-build/src/kraken/std/python/tasks/base_task.py index 6f028c70..17f8fdab 100644 --- a/kraken-build/src/kraken/std/python/tasks/base_task.py +++ b/kraken-build/src/kraken/std/python/tasks/base_task.py @@ -4,6 +4,7 @@ import os import shutil import subprocess as sp +import sys from collections.abc import Iterable, MutableMapping from deprecated import deprecated @@ -77,5 +78,6 @@ def execute(self) -> TaskStatus: logger.warning('%s = "*"', dep) return TaskStatus.failed("The %s dependencies are missing" % self.python_dependencies) logger.info("%s", command) - result = sp.call(command, cwd=self.project.directory, env=env) + shell = sys.platform.startswith("win32") # Windows requires shell to find executable in path + result = sp.call(command, cwd=self.project.directory, env=env, shell=shell) return self.handle_exit_code(result) diff --git a/kraken-build/src/kraken/std/python/tasks/mypy_task.py b/kraken-build/src/kraken/std/python/tasks/mypy_task.py index 3a6df8c2..b326d63b 100644 --- a/kraken-build/src/kraken/std/python/tasks/mypy_task.py +++ b/kraken-build/src/kraken/std/python/tasks/mypy_task.py @@ -1,5 +1,7 @@ from __future__ import annotations +import logging +import sys from collections.abc import MutableMapping, Sequence from pathlib import Path @@ -9,6 +11,8 @@ from .base_task import EnvironmentAwareDispatchTask +logger = logging.getLogger(__name__) + class MypyTask(EnvironmentAwareDispatchTask): description = "Static type checking for Python code using Mypy." @@ -24,7 +28,12 @@ class MypyTask(EnvironmentAwareDispatchTask): # EnvironmentAwareDispatchTask def get_execute_command_v2(self, env: MutableMapping[str, str]) -> list[str]: - entry_point = "dmypy" if self.use_daemon.get() else "mypy" + use_daemon = self.use_daemon.get() + if use_daemon and sys.platform.startswith("win32"): + use_daemon = False + logger.warning("Disable use of mypy daemon due to error in exit code on Windows") + + entry_point = "dmypy" if use_daemon else "mypy" if mypy_pex_bin := self.mypy_pex_bin.get(): # See https://pex.readthedocs.io/en/latest/api/vars.html @@ -38,7 +47,7 @@ def get_execute_command_v2(self, env: MutableMapping[str, str]) -> list[str]: # happens regularly but is hard to detect automatically). status_file = (self.project.directory / ".dmypy.json").absolute() - if self.use_daemon.get(): + if use_daemon: command += ["--status-file", str(status_file), "run", "--"] if mypy_pex_bin: # Have mypy pick up the Python executable from the virtual environment that is activated automatically