diff options
author | Bernát Gábor <bgabor8@bloomberg.net> | 2021-01-04 09:06:49 +0000 |
---|---|---|
committer | Bernát Gábor <bgabor8@bloomberg.net> | 2021-01-04 09:17:55 +0000 |
commit | 49e796ab200e2c259f5bebeb426d06e7595d5cd7 (patch) | |
tree | 2a9e42b704cb1adb6ea2599cfc92a8e28d28343b /src | |
parent | 4073c136d53029a00c4727975bf6c3755cb8a58c (diff) | |
download | tox-git-49e796ab200e2c259f5bebeb426d06e7595d5cd7.tar.gz |
Add tests for the package dependency marker filters
Signed-off-by: Bernát Gábor <bgabor8@bloomberg.net>
Diffstat (limited to 'src')
-rw-r--r-- | src/tox/tox_env/package.py | 4 | ||||
-rw-r--r-- | src/tox/tox_env/python/virtual_env/package/api.py | 58 | ||||
-rw-r--r-- | src/tox/util/pep517/via_fresh_subprocess.py | 68 |
3 files changed, 102 insertions, 28 deletions
diff --git a/src/tox/tox_env/package.py b/src/tox/tox_env/package.py index 86e822e6..a282e24f 100644 --- a/src/tox/tox_env/package.py +++ b/src/tox/tox_env/package.py @@ -4,7 +4,7 @@ A tox environment that can build packages. from abc import ABC, abstractmethod from argparse import ArgumentParser from pathlib import Path -from typing import TYPE_CHECKING, List, Optional, Set +from typing import TYPE_CHECKING, List, Set from packaging.requirements import Requirement @@ -30,7 +30,7 @@ class PackageToxEnv(ToxEnv, ABC): self.ref_count = AtomicCounter() @abstractmethod - def get_package_dependencies(self, extras: Optional[Set[str]] = None) -> List[Requirement]: + def get_package_dependencies(self, extras: Set[str]) -> List[Requirement]: raise NotImplementedError @abstractmethod diff --git a/src/tox/tox_env/python/virtual_env/package/api.py b/src/tox/tox_env/python/virtual_env/package/api.py index 377dd176..03ea06e8 100644 --- a/src/tox/tox_env/python/virtual_env/package/api.py +++ b/src/tox/tox_env/python/virtual_env/package/api.py @@ -136,40 +136,46 @@ class Pep517VirtualEnvPackage(VirtualEnv, PythonPackage, Frontend, ABC): self._package = self._build_artifact() return [self._package] - def get_package_dependencies(self, extras: Optional[Set[str]] = None) -> List[Requirement]: + def get_package_dependencies(self, extras: Set[str]) -> List[Requirement]: with self._lock: - if self._package_dependencies is None: - self._package_dependencies = self._load_package_dependencies(extras) + if self._package_dependencies is None: # pragma: no branch + self._ensure_meta_present() + self._package_dependencies = self.discover_package_dependencies(self._distribution_meta, extras) return self._package_dependencies - def _load_package_dependencies(self, extras: Optional[Set[str]]) -> List[Requirement]: - self._ensure_meta_present() - if extras is None: - extras = set() + @staticmethod + def discover_package_dependencies( # type: ignore[no-any-unimported] + meta: PathDistribution, extras: Set[str] + ) -> List[Requirement]: result: List[Requirement] = [] - if self._distribution_meta is None: - raise RuntimeError - requires = self._distribution_meta.requires or [] - for v in requires: - req = Requirement(v) + requires = meta.requires or [] + for req_str in requires: + req = Requirement(req_str) markers: List[Union[str, Tuple[Variable, Variable, Variable]]] = getattr(req.marker, "_markers", []) or [] - extra: Optional[str] = None + + # find the extra marker (if has) _at: Optional[int] = None - for _at, (m_key, op, m_val) in ( - (j, i) for j, i in enumerate(markers) if isinstance(i, tuple) and len(i) == 3 + extra: Optional[str] = None + for _at, (marker_key, op, marker_value) in ( + (_at_marker, marker) + for _at_marker, marker in enumerate(markers) + if isinstance(marker, tuple) and len(marker) == 3 ): - if m_key.value == "extra" and op.value == "==": - extra = m_val.value + if marker_key.value == "extra" and op.value == "==": # pragma: no branch + extra = marker_value.value break - if extra is None or extra in extras: - if _at is not None: + # continue only if this extra should be included + if not (extra is None or extra in extras): + continue + # delete the extra marker if present + if _at is not None: + del markers[_at] + _at -= 1 + if _at > 0 and (isinstance(markers[_at], str) and markers[_at] in ("and", "or")): del markers[_at] - _at -= 1 - if _at > 0 and (isinstance(markers[_at], str) and markers[_at] in ("and", "or")): - del markers[_at] - if len(markers) == 0: - req.marker = None - result.append(req) + if len(markers) == 0: + req.marker = None + result.append(req) return result @property @@ -211,7 +217,7 @@ class Pep517VirtualEnvPackage(VirtualEnv, PythonPackage, Frontend, ABC): self._backend_executor.close() @contextmanager - def _send_msg(self, cmd: str, result_file: Path, msg: str) -> Iterator[CmdStatus]: + def _send_msg(self, cmd: str, result_file: Path, msg: str) -> Iterator[ToxCmdStatus]: # type: ignore[override] with self.execute_async( cmd=self.backend_cmd, cwd=self._root, diff --git a/src/tox/util/pep517/via_fresh_subprocess.py b/src/tox/util/pep517/via_fresh_subprocess.py new file mode 100644 index 00000000..1f83a557 --- /dev/null +++ b/src/tox/util/pep517/via_fresh_subprocess.py @@ -0,0 +1,68 @@ +import os +import sys +from contextlib import contextmanager +from pathlib import Path +from subprocess import PIPE, Popen +from threading import Thread +from typing import IO, Any, Iterator, Optional, Tuple, cast + +from packaging.requirements import Requirement + +from .frontend import CmdStatus, Frontend + + +class SubprocessCmdStatus(CmdStatus, Thread): + def __init__(self, process: "Popen[str]") -> None: + super().__init__() + self.process = process + self._out_err: Optional[Tuple[str, str]] = None + self.start() + + def run(self) -> None: + self._out_err = self.process.communicate() + + @property + def done(self) -> bool: + return self.process.returncode is not None + + def out_err(self) -> Tuple[str, str]: + return cast(Tuple[str, str], self._out_err) + + +class SubprocessFrontend(Frontend): + def __init__( + self, + root: Path, + backend_paths: Tuple[Path, ...], + backend_module: str, + backend_obj: Optional[str], + requires: Tuple[Requirement, ...], + ): + super().__init__(root, backend_paths, backend_module, backend_obj, requires, reuse_backend=False) + + @contextmanager + def _send_msg( # type: ignore[override] + self, cmd: str, result_file: Path, msg: str + ) -> Iterator[SubprocessCmdStatus]: + env = os.environ.copy() + env["PYTHONPATH"] = os.pathsep.join(str(i) for i in self._backend_paths) + process = Popen( + args=[sys.executable] + self.backend_args, + stdout=PIPE, + stderr=PIPE, + stdin=PIPE, + universal_newlines=True, + cwd=self._root, + env=env, + ) + cast(IO[str], process.stdin).write(f"{os.linesep}{msg}{os.linesep}") + yield SubprocessCmdStatus(process) + + def send_cmd(self, cmd: str, **kwargs: Any) -> Tuple[Any, str, str]: + return self._send(cmd, **kwargs) + + +__all__ = ( + "SubprocessCmdStatus", + "SubprocessFrontend", +) |