-
Notifications
You must be signed in to change notification settings - Fork 9
Expand file tree
/
Copy pathhooks4.py
More file actions
260 lines (217 loc) · 7.68 KB
/
hooks4.py
File metadata and controls
260 lines (217 loc) · 7.68 KB
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
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
import argparse
import os
import platform
import sys
import sysconfig
from pathlib import Path
from typing import Set
from tox.config.loader.memory import MemoryLoader
from tox.execute.local_sub_process import (
Execute,
LocalSubProcessExecuteInstance,
)
from tox.plugin import impl
from tox.tox_env.python.api import PythonInfo
from tox.tox_env.python.runner import PythonRun
try:
import importlib.metadata as importlib_metadata
except ImportError:
import importlib_metadata
@impl
def tox_register_tox_env(register):
register.add_run_env(CurrentEnv)
register.add_run_env(PrintEnv)
@impl
def tox_add_option(parser):
parser.add_argument(
"--current-env",
action="store_true",
dest="current_env",
default=False,
help="Run tests in current environment, not creating any virtual environment",
)
parser.add_argument(
"--print-deps-to",
"--print-deps-to-file",
action="store",
type=argparse.FileType("w"),
metavar="FILE",
default=False,
help="Don't run tests, only print the dependencies to the given file "
+ "(use `-` for stdout)",
)
parser.add_argument(
"--print-extras-to",
"--print-extras-to-file",
action="store",
type=argparse.FileType("w"),
metavar="FILE",
default=False,
help="Don't run tests, only print the names of the required extras to the given file "
+ "(use `-` for stdout)",
)
@impl
def tox_add_core_config(core_conf, state):
opt = state.conf.options
if (opt.print_deps_to or opt.print_extras_to) and not core_conf.loaders:
raise RuntimeError(
"--print-deps-to and/or --print-extras-to cannot be used without a tox config. "
"Seeing this error indicates this project either does not use tox at all or the tox configuration is missing."
)
if opt.current_env or opt.print_deps_to or opt.print_extras_to:
# We do not want to install the main package.
# no_package is the same as skipsdist.
loader = MemoryLoader(no_package=True)
core_conf.loaders.insert(0, loader)
if opt.current_env:
opt.default_runner = "current-env"
return
if getattr(opt.print_deps_to, "name", object()) == getattr(
opt.print_extras_to, "name", object()
):
raise RuntimeError(
"The paths given to --print-deps-to and --print-extras-to cannot be identical."
)
if opt.print_deps_to or opt.print_extras_to:
opt.default_runner = "print-env"
return
# No options used - switch back to the standard runner
# Workaround for: https://github.com/tox-dev/tox/issues/2264
opt.default_runner = "virtualenv"
@impl
def tox_add_env_config(env_conf, state):
opt = state.conf.options
# This allows all external commands.
# All of them are external for us.
# Because tox 4 no longer reads $TOX_TESTENV_PASSENV,
# this plugin always passes all environment variables by default.
if opt.current_env:
allow_external_cmds = MemoryLoader(allowlist_externals=["*"], pass_env=["*"])
env_conf.loaders.insert(0, allow_external_cmds)
# For print-deps-to and print-extras-to, use empty
# list of commands so the tox does nothing.
if opt.print_deps_to or opt.print_extras_to:
empty_commands = MemoryLoader(commands=[], commands_pre=[], commands_post=[])
env_conf.loaders.insert(0, empty_commands)
class Installer:
"""Noop installer"""
def install(self, *args, **kwargs):
return None
def installed(self):
"""Return list of installed packages like `pip freeze`."""
return [
"{}=={}".format(d.metadata.get("name"), d.version)
for d in sorted(
importlib_metadata.distributions(), key=lambda d: d.metadata.get("name")
)
]
class CurrentEnvLocalSubProcessExecutor(Execute):
def build_instance(
self,
request,
options,
out,
err,
):
request.env["PATH"] = ":".join(
(str(options._env.env_dir / "bin"), request.env.get("PATH", ""))
)
return LocalSubProcessExecuteInstance(request, options, out, err)
class CurrentEnv(PythonRun):
def __init__(self, create_args):
self._executor = None
self._installer = None
self._path = []
super().__init__(create_args)
@staticmethod
def id():
return "current-env"
@property
def _default_package_tox_env_type(self):
return None
@property
def _external_pkg_tox_env_type(self):
return None
@property
def _package_tox_env_type(self):
return None
@property
def executor(self):
if self._executor is None:
self._executor = CurrentEnvLocalSubProcessExecutor(self.options.is_colored)
return self._executor
def _get_python(self, base_python):
return PythonInfo(
implementation=sys.implementation,
version_info=sys.version_info,
version=sys.version,
is_64=(platform.architecture()[0] == "64bit"),
platform=platform.platform(),
extra={"executable": Path(sys.executable)},
)
def create_python_env(self):
"""Fake Python environment just to make sure all possible
commands like python or python3 works."""
bindir = self.env_dir / "bin"
if not bindir.exists():
os.mkdir(bindir)
for suffix in (
"",
f"{sys.version_info.major}",
f"{sys.version_info.major}.{sys.version_info.minor}",
):
symlink = bindir / f"python{suffix}"
if not symlink.exists():
os.symlink(sys.executable, symlink)
def env_bin_dir(self):
return Path(sysconfig.get_path("scripts"))
def env_python(self):
return sys.executable
def env_site_package_dir(self):
return Path(sysconfig.get_path("purelib"))
@property
def installer(self):
return Installer()
def prepend_env_var_path(self):
return [self.env_bin_dir()]
@property
def runs_on_platform(self):
return sys.platform
class PrintEnv(CurrentEnv):
def __init__(self, create_args):
super().__init__(create_args)
if self.options.print_extras_to:
if "extras" not in self.conf:
# Unfortunately, if there is skipsdist/no_package or skip_install
# in the config, this section is not parsed at all so we have to
# do it here manually to be able to read its content.
self.conf.add_config(
keys=["extras"],
of_type=Set[str],
default=set(),
desc="extras to install of the target package",
)
def create_python_env(self):
"""We don't need any environment for this plugin"""
return None
def prepend_env_var_path(self):
"""Usage of this method for the core of this plugin is far from perfect
but this method is called every time even without recreated environment"""
if self.options.print_deps_to:
print(
*self.core["requires"],
*self.conf["deps"].lines(),
sep="\n",
file=self.options.print_deps_to,
)
self.options.print_deps_to.flush()
if self.options.print_extras_to:
print(
*self.conf["extras"],
sep="\n",
file=self.options.print_extras_to,
)
self.options.print_extras_to.flush()
@staticmethod
def id():
return "print-env"