summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docs/changelog/1847.feature.rst2
-rw-r--r--docs/changelog/1848.feature.rst2
-rw-r--r--docs/changelog/1849.feature.rst1
-rw-r--r--setup.cfg1
-rw-r--r--src/tox/config/cli/parser.py2
-rw-r--r--src/tox/execute/local_sub_process/read_via_thread_windows.py2
-rw-r--r--src/tox/plugin/manager.py2
-rw-r--r--src/tox/tox_env/api.py18
-rw-r--r--src/tox/tox_env/python/api.py10
-rw-r--r--src/tox/tox_env/python/virtual_env/api.py69
-rw-r--r--src/tox/tox_env/python/virtual_env/package/api.py6
-rw-r--r--tests/config/test_set_env.py4
-rw-r--r--tests/session/cmd/test_show_config.py4
-rw-r--r--tests/test_provision.py2
-rw-r--r--tests/tox_env/python/virtual_env/test_package.py4
-rw-r--r--tests/tox_env/python/virtual_env/test_virtualenv_api.py107
16 files changed, 200 insertions, 36 deletions
diff --git a/docs/changelog/1847.feature.rst b/docs/changelog/1847.feature.rst
new file mode 100644
index 00000000..3be70211
--- /dev/null
+++ b/docs/changelog/1847.feature.rst
@@ -0,0 +1,2 @@
+Support the ``system_site_packages``/``sitepackages`` flag for virtual environment based tox environments -
+by :user:`gaborbernat`.
diff --git a/docs/changelog/1848.feature.rst b/docs/changelog/1848.feature.rst
new file mode 100644
index 00000000..31000a91
--- /dev/null
+++ b/docs/changelog/1848.feature.rst
@@ -0,0 +1,2 @@
+Support the ``always_copy``/``alwayscopy`` flag for virtual environment based tox environments -
+by :user:`gaborbernat`.
diff --git a/docs/changelog/1849.feature.rst b/docs/changelog/1849.feature.rst
new file mode 100644
index 00000000..add6fbfd
--- /dev/null
+++ b/docs/changelog/1849.feature.rst
@@ -0,0 +1 @@
+Support the ``download`` flag for virtual environment based tox environments - by :user:`gaborbernat`.
diff --git a/setup.cfg b/setup.cfg
index c5590f19..c56afc5e 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -112,7 +112,6 @@ subtract_omit = */.tox/*
[mypy]
python_version = 3.6
-disallow_any_unimported = True
disallow_any_generics = True
disallow_subclassing_any = True
disallow_untyped_calls = True
diff --git a/src/tox/config/cli/parser.py b/src/tox/config/cli/parser.py
index b1804793..f47029ba 100644
--- a/src/tox/config/cli/parser.py
+++ b/src/tox/config/cli/parser.py
@@ -87,7 +87,7 @@ class HelpFormatter(ArgumentDefaultsHelpFormatter):
text: str = super()._get_help_string(action) or "" # noqa
if hasattr(action, "default_source"):
default = " (default: %(default)s)"
- if text.endswith(default):
+ if text.endswith(default): # pragma: no branch
text = f"{text[: -len(default)]} (default: %(default)s -> from %(default_source)s)"
return text
diff --git a/src/tox/execute/local_sub_process/read_via_thread_windows.py b/src/tox/execute/local_sub_process/read_via_thread_windows.py
index 9bec8962..1f4d29a1 100644
--- a/src/tox/execute/local_sub_process/read_via_thread_windows.py
+++ b/src/tox/execute/local_sub_process/read_via_thread_windows.py
@@ -15,7 +15,7 @@ class ReadViaThreadWindows(ReadViaThread): # pragma: win32 cover
def __init__(self, file_no: int, handler: Callable[[bytes], None], name: str, drain: bool) -> None:
super().__init__(file_no, handler, name, drain)
self.closed = False
- self._ov: Optional[_overlapped.Overlapped] = None # type: ignore[no-any-unimported]
+ self._ov: Optional[_overlapped.Overlapped] = None
self._waiting_for_read = False
def _read_stream(self) -> None:
diff --git a/src/tox/plugin/manager.py b/src/tox/plugin/manager.py
index ee3e42f8..55edb57c 100644
--- a/src/tox/plugin/manager.py
+++ b/src/tox/plugin/manager.py
@@ -21,7 +21,7 @@ from . import NAME, spec
class Plugin:
def __init__(self) -> None:
- self.manager: pluggy.PluginManager = pluggy.PluginManager(NAME) # type: ignore[no-any-unimported]
+ self.manager: pluggy.PluginManager = pluggy.PluginManager(NAME)
self.manager.add_hookspecs(spec)
internal_plugins = (
diff --git a/src/tox/tox_env/api.py b/src/tox/tox_env/api.py
index fe8888a5..1bff9fec 100644
--- a/src/tox/tox_env/api.py
+++ b/src/tox/tox_env/api.py
@@ -210,10 +210,26 @@ class ToxEnv(ABC):
set_env: SetEnv = self.conf["set_env"]
for key in set_env:
result[key] = set_env.load(key)
- result["PATH"] = os.pathsep.join([str(i) for i in self._paths] + os.environ.get("PATH", "").split(os.pathsep))
+ result["PATH"] = self.paths_env()
self._env_vars = result
return result
+ @property
+ def paths(self) -> List[Path]:
+ return self._paths
+
+ @paths.setter
+ def paths(self, value: List[Path]) -> None:
+ self._paths = value
+ if self._env_vars is not None: # pragma: no branch # also update the environment variables with the new value
+ self._env_vars["PATH"] = self.paths_env()
+
+ def paths_env(self) -> str:
+ # remove duplicates and prepend the tox env paths
+ values = dict.fromkeys(str(i) for i in self.paths)
+ values.update(dict.fromkeys(os.environ.get("PATH", "").split(os.pathsep)))
+ return os.pathsep.join(values)
+
def execute(
self,
cmd: Sequence[Union[Path, str]],
diff --git a/src/tox/tox_env/python/api.py b/src/tox/tox_env/python/api.py
index f2950dd7..cc2ffd68 100644
--- a/src/tox/tox_env/python/api.py
+++ b/src/tox/tox_env/python/api.py
@@ -158,9 +158,13 @@ class Python(ToxEnv, ABC):
with self._cache.compare(conf, Python.__name__) as (eq, old):
if eq is False: # if changed create
self.create_python_env()
- self._paths = self.paths()
+ self.paths = self.python_env_paths() # now that the environment exist we can add them to the path
super().setup()
+ @abstractmethod
+ def python_env_paths(self) -> List[Path]:
+ raise NotImplementedError
+
def setup_has_been_done(self) -> None:
"""called when setup is done"""
super().setup_has_been_done()
@@ -228,10 +232,6 @@ class Python(ToxEnv, ABC):
raise NotImplementedError
@abstractmethod
- def paths(self) -> List[Path]:
- raise NotImplementedError
-
- @abstractmethod
def install_python_packages(self, packages: PythonDeps, of_type: str, no_deps: bool = False) -> None:
raise NotImplementedError
diff --git a/src/tox/tox_env/python/virtual_env/api.py b/src/tox/tox_env/python/virtual_env/api.py
index 5af0a504..fb28a5b7 100644
--- a/src/tox/tox_env/python/virtual_env/api.py
+++ b/src/tox/tox_env/python/virtual_env/api.py
@@ -10,6 +10,7 @@ from virtualenv.create.creator import Creator
from virtualenv.run.session import Session
from tox.config.cli.parser import DEFAULT_VERBOSITY, Parsed
+from tox.config.loader.str_convert import StrConvert
from tox.config.sets import CoreConfigSet, EnvConfigSet
from tox.execute.api import Execute, Outcome, StdinSource
from tox.execute.local_sub_process import LocalSubProcessExecutor
@@ -25,9 +26,38 @@ class VirtualEnv(Python, ABC):
def __init__(
self, conf: EnvConfigSet, core: CoreConfigSet, options: Parsed, journal: EnvJournal, log_handler: ToxHandler
) -> None:
- self._virtualenv_session: Optional[Session] = None # type: ignore[no-any-unimported]
+ self._virtualenv_session: Optional[Session] = None
super().__init__(conf, core, options, journal, log_handler)
+ def register_config(self) -> None:
+ super().register_config()
+ self.conf.add_config(
+ keys=["system_site_packages", "sitepackages"],
+ of_type=bool,
+ default=lambda conf, name: StrConvert().to_bool(
+ self.environment_variables.get("VIRTUALENV_SYSTEM_SITE_PACKAGES", "False")
+ ),
+ desc="create virtual environments that also have access to globally installed packages.",
+ )
+ self.conf.add_config(
+ keys=["always_copy", "alwayscopy"],
+ of_type=bool,
+ default=lambda conf, name: StrConvert().to_bool(
+ self.environment_variables.get(
+ "VIRTUALENV_COPIES", self.environment_variables.get("VIRTUALENV_ALWAYS_COPY", "False")
+ )
+ ),
+ desc="force virtualenv to always copy rather than symlink",
+ )
+ self.conf.add_config(
+ keys=["download"],
+ of_type=bool,
+ default=lambda conf, name: StrConvert().to_bool(
+ self.environment_variables.get("VIRTUALENV_DOWNLOAD", "False")
+ ),
+ desc="true if you want virtualenv to upgrade pip/wheel/setuptools to the latest version",
+ )
+
def default_pass_env(self) -> List[str]:
env = super().default_pass_env()
env.append("PIP_*") # we use pip as installer
@@ -37,28 +67,37 @@ class VirtualEnv(Python, ABC):
def default_set_env(self) -> Dict[str, str]:
env = super().default_set_env()
env["PIP_DISABLE_PIP_VERSION_CHECK"] = "1"
- env["VIRTUALENV_NO_PERIODIC_UPDATE"] = "1"
return env
def build_executor(self) -> Execute:
return LocalSubProcessExecutor(self.options.is_colored)
@property
- def session(self) -> Session: # type: ignore[no-any-unimported]
+ def session(self) -> Session:
if self._virtualenv_session is None:
- args = [
- "--clear",
- "--no-periodic-update",
- str(cast(Path, self.conf["env_dir"])),
- ]
- base_python: List[str] = self.conf["base_python"]
- for base in base_python:
- args.extend(["-p", base])
- self._virtualenv_session = session_via_cli(args, setup_logging=False)
+ self._virtualenv_session = session_via_cli(
+ [str(cast(Path, self.conf["env_dir"]))],
+ options=None,
+ setup_logging=False,
+ env=self.virtualenv_env_vars(),
+ )
return self._virtualenv_session
+ def virtualenv_env_vars(self) -> Dict[str, str]:
+ env = self.environment_variables.copy()
+ base_python: List[str] = self.conf["base_python"]
+ if "VIRTUALENV_CLEAR" not in env:
+ env["VIRTUALENV_CLEAR"] = "True"
+ if "VIRTUALENV_NO_PERIODIC_UPDATE" not in env:
+ env["VIRTUALENV_NO_PERIODIC_UPDATE"] = "True"
+ env["VIRTUALENV_SYSTEM_SITE_PACKAGES"] = str(self.conf["system_site_packages"])
+ env["VIRTUALENV_COPIES"] = str(self.conf["always_copy"])
+ env["VIRTUALENV_DOWNLOAD"] = str(self.conf["download"])
+ env["VIRTUALENV_PYTHON"] = "\n".join(base_python)
+ return env
+
@property
- def creator(self) -> Creator: # type: ignore[no-any-unimported]
+ def creator(self) -> Creator:
return self.session.creator
def create_python_env(self) -> None:
@@ -79,10 +118,10 @@ class VirtualEnv(Python, ABC):
extra_version_info=None,
)
- def paths(self) -> List[Path]:
+ def python_env_paths(self) -> List[Path]:
"""Paths to add to the executable"""
# we use the original executable as shims may be somewhere else
- return list({self.creator.bin_dir, self.creator.script_dir})
+ return list(dict.fromkeys((self.creator.bin_dir, self.creator.script_dir)))
def env_site_package_dir(self) -> Path:
return cast(Path, self.creator.purelib)
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 8bf28c5b..b3a0a11b 100644
--- a/src/tox/tox_env/python/virtual_env/package/api.py
+++ b/src/tox/tox_env/python/virtual_env/package/api.py
@@ -90,7 +90,7 @@ class Pep517VirtualEnvPackage(VirtualEnv, PythonPackage, Frontend):
) -> None:
VirtualEnv.__init__(self, conf, core, options, journal, log_handler)
Frontend.__init__(self, *Frontend.create_args_from_folder(core["tox_root"]))
- self._distribution_meta: Optional[PathDistribution] = None # type: ignore[no-any-unimported]
+ self._distribution_meta: Optional[PathDistribution] = None
self._build_requires: Optional[Tuple[Requirement]] = None
self._build_wheel_cache: Optional[WheelResult] = None
self._backend_executor: Optional[LocalSubProcessPep517Executor] = None
@@ -217,9 +217,7 @@ class Pep517VirtualEnvPackage(VirtualEnv, PythonPackage, Frontend):
return self._package_dependencies
@staticmethod
- def discover_package_dependencies( # type: ignore[no-any-unimported]
- meta: PathDistribution, extras: Set[str]
- ) -> List[Requirement]:
+ def discover_package_dependencies(meta: PathDistribution, extras: Set[str]) -> List[Requirement]:
result: List[Requirement] = []
requires = meta.requires or []
for req_str in requires:
diff --git a/tests/config/test_set_env.py b/tests/config/test_set_env.py
index df59830b..ef6ae8d3 100644
--- a/tests/config/test_set_env.py
+++ b/tests/config/test_set_env.py
@@ -44,9 +44,9 @@ def eval_set_env(tox_project: ToxProjectCreator) -> EvalSetEnv:
def test_set_env_default(eval_set_env: EvalSetEnv, monkeypatch: MonkeyPatch) -> None:
set_env = eval_set_env("")
keys = list(set_env)
- assert keys == ["PIP_DISABLE_PIP_VERSION_CHECK", "VIRTUALENV_NO_PERIODIC_UPDATE"]
+ assert keys == ["PIP_DISABLE_PIP_VERSION_CHECK"]
values = [set_env.load(k) for k in keys]
- assert values == ["1", "1"]
+ assert values == ["1"]
def test_set_env_self_key(eval_set_env: EvalSetEnv, monkeypatch: MonkeyPatch) -> None:
diff --git a/tests/session/cmd/test_show_config.py b/tests/session/cmd/test_show_config.py
index 7c196ee8..1cd3fcda 100644
--- a/tests/session/cmd/test_show_config.py
+++ b/tests/session/cmd/test_show_config.py
@@ -45,7 +45,7 @@ def test_show_config_commands(tox_project: ToxProjectCreator) -> None:
)
outcome = project.run("c")
outcome.assert_success()
- env_config = outcome.state.tox_env("py").conf
+ env_config = outcome.env_conf("py")
assert env_config["commands_pre"] == [Command(args=["python", "-c", 'import sys; print("start", sys.executable)'])]
assert env_config["commands"] == [
Command(args=["pip", "config", "list"]),
@@ -90,7 +90,7 @@ def test_pass_env_config_default(tox_project: ToxProjectCreator, stdout_is_atty:
mocker.patch("sys.stdout.isatty", return_value=stdout_is_atty)
project = tox_project({"tox.ini": ""})
outcome = project.run("c", "-e", "py", "-k", "pass_env")
- pass_env = outcome.state.tox_env("py").conf["pass_env"]
+ pass_env = outcome.env_conf("py")["pass_env"]
is_win = sys.platform == "win32"
expected = (
(["COMSPEC"] if is_win else [])
diff --git a/tests/test_provision.py b/tests/test_provision.py
index c08024ae..365ac7a5 100644
--- a/tests/test_provision.py
+++ b/tests/test_provision.py
@@ -124,7 +124,7 @@ def test_provision_requires_ok(
assert "py" in log_report["testenvs"]
# recreate without recreating the provisioned env
- provision_env = result_first.state.tox_env(".tox").conf["env_dir"]
+ provision_env = result_first.env_conf(".tox")["env_dir"]
result_recreate_no_pr = proj.run("r", "--recreate", "--no-recreate-provision")
result_recreate_no_pr.assert_success()
assert prov_msg in result_recreate_no_pr.out
diff --git a/tests/tox_env/python/virtual_env/test_package.py b/tests/tox_env/python/virtual_env/test_package.py
index 378a6fb7..9ba1f14e 100644
--- a/tests/tox_env/python/virtual_env/test_package.py
+++ b/tests/tox_env/python/virtual_env/test_package.py
@@ -23,9 +23,9 @@ def test_tox_ini_package_type_valid(tox_project: ToxProjectCreator, pkg_type: st
proj = tox_project({"tox.ini": f"[testenv]\npackage={pkg_type}"})
result = proj.run("c", "-k", "package_tox_env_type")
result.assert_success()
- res = result.state.tox_env("py").conf["package"]
+ res = result.env_conf("py")["package"]
assert res is getattr(PackageType, pkg_type)
- got_type = result.state.tox_env("py").conf["package_tox_env_type"]
+ got_type = result.env_conf("py")["package_tox_env_type"]
assert got_type == "virtualenv-pep-517"
diff --git a/tests/tox_env/python/virtual_env/test_virtualenv_api.py b/tests/tox_env/python/virtual_env/test_virtualenv_api.py
new file mode 100644
index 00000000..8320b9dc
--- /dev/null
+++ b/tests/tox_env/python/virtual_env/test_virtualenv_api.py
@@ -0,0 +1,107 @@
+import os
+
+import pytest
+from pytest_mock import MockerFixture
+from virtualenv import session_via_cli
+from virtualenv.config.cli.parser import VirtualEnvOptions
+
+from tox.pytest import MonkeyPatch, ToxProject, ToxProjectCreator
+
+
+@pytest.fixture()
+def virtualenv_opt(monkeypatch: MonkeyPatch, mocker: MockerFixture) -> VirtualEnvOptions:
+ for key in os.environ:
+ if key.startswith("VIRTUALENV_"): # pragma: no cover
+ monkeypatch.delenv(key) # pragma: no cover
+ opts = VirtualEnvOptions()
+ mocker.patch(
+ "tox.tox_env.python.virtual_env.api.session_via_cli",
+ side_effect=lambda args, options, setup_logging, env: session_via_cli(args, opts, setup_logging, env),
+ )
+ return opts
+
+
+def test_virtualenv_default_settings(tox_project: ToxProjectCreator, virtualenv_opt: VirtualEnvOptions) -> None:
+ proj = tox_project({"tox.ini": "[testenv]\npackage=skip"})
+ result = proj.run("r", "-e", "py")
+ result.assert_success()
+
+ conf = result.env_conf("py")
+ assert conf["system_site_packages"] is False
+ assert conf["always_copy"] is False
+ assert conf["download"] is False
+
+ assert virtualenv_opt.clear is True
+ assert virtualenv_opt.system_site is False
+ assert virtualenv_opt.download is False
+ assert virtualenv_opt.copies is False
+ assert virtualenv_opt.no_periodic_update is True
+ assert virtualenv_opt.python == ["py"]
+
+
+def test_virtualenv_flipped_settings(
+ tox_project: ToxProjectCreator, virtualenv_opt: VirtualEnvOptions, monkeypatch: MonkeyPatch
+) -> None:
+ proj = tox_project(
+ {"tox.ini": "[testenv]\npackage=skip\nsystem_site_packages=True\nalways_copy=True\ndownload=True"}
+ )
+ monkeypatch.setenv("VIRTUALENV_CLEAR", "0")
+
+ result = proj.run("r", "-e", "py")
+ result.assert_success()
+
+ conf = result.env_conf("py")
+ assert conf["system_site_packages"] is True
+ assert conf["always_copy"] is True
+ assert conf["download"] is True
+
+ assert virtualenv_opt.clear is False
+ assert virtualenv_opt.system_site is True
+ assert virtualenv_opt.download is True
+ assert virtualenv_opt.copies is True
+ assert virtualenv_opt.python == ["py"]
+
+
+def test_virtualenv_env_ignored_if_set(
+ tox_project: ToxProjectCreator, virtualenv_opt: VirtualEnvOptions, monkeypatch: MonkeyPatch
+) -> None:
+ ini = "[testenv]\npackage=skip\nsystem_site_packages=True\nalways_copy=True\ndownload=True"
+ proj = tox_project({"tox.ini": ini})
+ monkeypatch.setenv("VIRTUALENV_COPIES", "0")
+ monkeypatch.setenv("VIRTUALENV_DOWNLOAD", "0")
+ monkeypatch.setenv("VIRTUALENV_SYSTEM_SITE_PACKAGES", "0")
+ run_and_check_set(proj, virtualenv_opt)
+
+
+def test_virtualenv_env_used_if_not_set(
+ tox_project: ToxProjectCreator, virtualenv_opt: VirtualEnvOptions, monkeypatch: MonkeyPatch
+) -> None:
+ proj = tox_project({"tox.ini": "[testenv]\npackage=skip"})
+ monkeypatch.setenv("VIRTUALENV_COPIES", "1")
+ monkeypatch.setenv("VIRTUALENV_DOWNLOAD", "1")
+ monkeypatch.setenv("VIRTUALENV_SYSTEM_SITE_PACKAGES", "1")
+ run_and_check_set(proj, virtualenv_opt)
+
+
+def run_and_check_set(proj: ToxProject, virtualenv_opt: VirtualEnvOptions) -> None:
+ result = proj.run("r", "-e", "py")
+ result.assert_success()
+ conf = result.env_conf("py")
+ assert conf["system_site_packages"] is True
+ assert conf["always_copy"] is True
+ assert conf["download"] is True
+ assert virtualenv_opt.system_site is True
+ assert virtualenv_opt.download is True
+ assert virtualenv_opt.copies is True
+
+
+def test_honor_set_env_for_clear_periodic_update(
+ tox_project: ToxProjectCreator, virtualenv_opt: VirtualEnvOptions, monkeypatch: MonkeyPatch
+) -> None:
+ ini = "[testenv]\npackage=skip\nset_env=\n VIRTUALENV_CLEAR=0\n VIRTUALENV_NO_PERIODIC_UPDATE=0"
+ proj = tox_project({"tox.ini": ini})
+ result = proj.run("r", "-e", "py")
+ result.assert_success()
+
+ assert virtualenv_opt.clear is False
+ assert virtualenv_opt.no_periodic_update is False