summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorBernát Gábor <bgabor8@bloomberg.net>2021-01-21 09:42:45 +0000
committerGitHub <noreply@github.com>2021-01-21 09:42:45 +0000
commit1476fa617aca7563201b62df495767fa7bf40b70 (patch)
tree1369506b9bb58a75fccf3c58a602d1df3b066514 /src
parent5f172bc47b85dc24c706a2f33c3917089e0554f4 (diff)
downloadtox-git-1476fa617aca7563201b62df495767fa7bf40b70.tar.gz
Add support for virtualenv configuration options (#1862)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Diffstat (limited to 'src')
-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
7 files changed, 81 insertions, 28 deletions
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: