-
Notifications
You must be signed in to change notification settings - Fork 8
/
hooks.py
184 lines (154 loc) · 6.28 KB
/
hooks.py
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
import os
import shutil
import sys
import tox
@tox.hookimpl
def tox_addoption(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-only",
action="store_true",
dest="print_deps_only",
default=False,
help="Don't run tests, only print the dependencies to stdout",
)
parser.add_argument(
"--print-deps-to-file",
action="store",
dest="print_deps_path",
metavar="PATH",
default=None,
help="Like --print-deps-only, but to a file. Overwrites the file if it exists.",
)
@tox.hookimpl
def tox_configure(config):
"""Stores options in the config. Makes all commands external and skips sdist"""
if config.option.print_deps_only and config.option.print_deps_path:
raise tox.exception.ConfigError(
"--print-deps-only cannot be used together with --print-deps-to-file"
)
if config.option.print_deps_path is not None:
config.option.print_deps_only = True
with open(config.option.print_deps_path, "w", encoding="utf-8") as f:
f.write("")
if config.option.current_env or config.option.print_deps_only:
config.skipsdist = True
for testenv in config.envconfigs:
config.envconfigs[testenv].whitelist_externals = "*"
return config
class InterpreterMismatch(tox.exception.InterpreterNotFound):
"""Interpreter version in current env does not match requested version"""
def _python_activate_exists(venv):
python = venv.envconfig.get_envpython()
bindir = os.path.dirname(python)
activate = os.path.join(bindir, "activate")
return os.path.exists(python), os.path.exists(activate)
def is_current_env_link(venv):
python, activate = _python_activate_exists(venv)
return python and not activate
def is_proper_venv(venv):
python, activate = _python_activate_exists(venv)
return python and activate
def is_any_env(venv):
python, activate = _python_activate_exists(venv)
return python
def rm_venv(venv):
link = venv.envconfig.get_envpython()
shutil.rmtree(os.path.dirname(os.path.dirname(link)), ignore_errors=True)
def unsupported_raise(config, venv):
if config.option.recreate:
return
regular = not (config.option.current_env or config.option.print_deps_only)
if regular and is_current_env_link(venv):
if hasattr(tox.hookspecs, "tox_cleanup"):
raise tox.exception.ConfigError(
"Looks like previous --current-env or --print-deps-only tox run didn't finish the cleanup. "
"Run tox run with --recreate (-r) or manually remove the environment in .tox."
)
else:
raise tox.exception.ConfigError(
"Regular tox run after --current-env or --print-deps-only tox run is not supported without --recreate (-r)."
)
elif config.option.current_env and is_proper_venv(venv):
raise tox.exception.ConfigError(
"--current-env after regular tox run is not supported without --recreate (-r)."
)
@tox.hookimpl
def tox_testenv_create(venv, action):
"""We create a fake virtualenv with just the symbolic link"""
config = venv.envconfig.config
create_fake_env = check_version = config.option.current_env
if config.option.print_deps_only:
if is_any_env(venv):
# We don't need anything
return True
else:
# We need at least some kind of environment,
# or tox fails without a python command
# We fallback to --current-env behavior,
# because it's cheaper, faster and won't install stuff
create_fake_env = True
if check_version:
# With real --current-env, we check this, but not with --print-deps-only only
version_info = venv.envconfig.python_info.version_info
if version_info is None:
raise tox.exception.InterpreterNotFound(venv.envconfig.basepython)
if version_info[:2] != sys.version_info[:2]:
raise InterpreterMismatch(
f"tox_current_env: interpreter versions do not match:\n"
+ f" in current env: {tuple(sys.version_info)}\n"
+ f" requested: {version_info}"
)
if create_fake_env:
# Make sure the `python` command on path is sys.executable.
# (We might have e.g. /usr/bin/python3, not `python`.)
# Remove the rest of the virtualenv.
link = venv.envconfig.get_envpython()
target = sys.executable
rm_venv(venv)
os.makedirs(os.path.dirname(link))
os.symlink(target, link)
# prevent tox from creating the venv
return True
if not is_proper_venv(venv):
rm_venv(venv)
return None # let tox handle the rest
@tox.hookimpl
def tox_package(session, venv):
"""Fail early when unsupported"""
config = venv.envconfig.config
unsupported_raise(config, venv)
@tox.hookimpl
def tox_testenv_install_deps(venv, action):
"""We don't install anything"""
config = venv.envconfig.config
unsupported_raise(config, venv)
if config.option.current_env or config.option.print_deps_only:
return True
@tox.hookimpl
def tox_runtest(venv, redirect):
"""If --print-deps-only, prints deps instead of running tests"""
config = venv.envconfig.config
unsupported_raise(config, venv)
if config.option.print_deps_path is not None:
with open(config.option.print_deps_path, "a", encoding="utf-8") as f:
print(*venv.get_resolved_dependencies(), sep="\n", file=f)
return True
if config.option.print_deps_only:
print(*venv.get_resolved_dependencies(), sep="\n")
return True
if hasattr(tox.hookspecs, "tox_cleanup"):
@tox.hookimpl
def tox_cleanup(session):
"""Remove the fake virtualenv not to collide with regular tox
Collisions can happen anyway (when tox is killed forcefully before this happens)
Note that we don't remove real venvs, as recreating them is expensive"""
for venv in session.venv_dict.values():
if is_current_env_link(venv):
rm_venv(venv)