1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
|
"""
Declare the abstract base class for tox environments that handle the Python language via the virtualenv project.
"""
from __future__ import annotations
import os
import sys
from pathlib import Path
from typing import Any, cast
from virtualenv import __version__ as virtualenv_version
from virtualenv import session_via_cli
from virtualenv.create.creator import Creator
from virtualenv.run.session import Session
from tox.config.loader.str_convert import StrConvert
from tox.execute.api import Execute
from tox.execute.local_sub_process import LocalSubProcessExecutor
from tox.tox_env.python.pip.pip_install import Pip
from ...api import ToxEnvCreateArgs
from ..api import Python, PythonInfo
class VirtualEnv(Python):
"""A python executor that uses the virtualenv project with pip"""
def __init__(self, create_args: ToxEnvCreateArgs) -> None:
self._virtualenv_session: Session | None = None
self._executor: Execute | None = None
self._installer: Pip | None = None
super().__init__(create_args)
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( # noqa: U100
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( # noqa: U100
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( # noqa: U100
self.environment_variables.get("VIRTUALENV_DOWNLOAD", "False"),
),
desc="true if you want virtualenv to upgrade pip/wheel/setuptools to the latest version",
)
@property
def executor(self) -> Execute:
if self._executor is None:
self._executor = LocalSubProcessExecutor(self.options.is_colored)
return self._executor
@property
def installer(self) -> Pip:
if self._installer is None:
self._installer = Pip(self)
return self._installer
def python_cache(self) -> dict[str, Any]:
base = super().python_cache()
base.update(
{
"executable": str(self.base_python.extra["executable"]),
"virtualenv version": virtualenv_version,
},
)
return base
def _get_env_journal_python(self) -> dict[str, Any]:
base = super()._get_env_journal_python()
base["executable"] = str(self.base_python.extra["executable"])
return base
def _default_pass_env(self) -> list[str]:
env = super()._default_pass_env()
env.append("PIP_*") # we use pip as installer
env.append("VIRTUALENV_*") # we use virtualenv as isolation creator
return env
def _default_set_env(self) -> dict[str, str]:
env = super()._default_set_env()
env["PIP_DISABLE_PIP_VERSION_CHECK"] = "1"
return env
@property
def session(self) -> Session:
if self._virtualenv_session is None:
env_dir = [str(self.env_dir)]
env = self.virtualenv_env_vars()
self._virtualenv_session = session_via_cli(env_dir, options=None, setup_logging=False, env=env)
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_NO_PERIODIC_UPDATE" not in env:
env["VIRTUALENV_NO_PERIODIC_UPDATE"] = "True"
env["VIRTUALENV_CLEAR"] = "False"
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)
if hasattr(self.options, "discover"):
env["VIRTUALENV_TRY_FIRST_WITH"] = os.pathsep.join(self.options.discover)
return env
@property
def creator(self) -> Creator:
return self.session.creator
def create_python_env(self) -> None:
self.session.run()
def _get_python(self, base_python: list[str]) -> PythonInfo | None: # noqa: U100
# the base pythons are injected into the virtualenv_env_vars, so we don't need to use it here
try:
interpreter = self.creator.interpreter
except RuntimeError: # if can't find
return None
return PythonInfo(
implementation=interpreter.implementation,
version_info=interpreter.version_info,
version=interpreter.version,
is_64=(interpreter.architecture == 64),
platform=interpreter.platform,
extra={"executable": Path(interpreter.system_executable).resolve()},
)
def prepend_env_var_path(self) -> list[Path]:
"""Paths to add to the executable"""
# we use the original executable as shims may be somewhere else
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)
def env_python(self) -> Path:
return cast(Path, self.creator.exe)
def env_bin_dir(self) -> Path:
return cast(Path, self.creator.script_dir)
@property
def runs_on_platform(self) -> str:
return sys.platform
@property
def environment_variables(self) -> dict[str, str]:
environment_variables = super().environment_variables
environment_variables["VIRTUAL_ENV"] = str(self.conf["env_dir"])
return environment_variables
|