csspin-python 2.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- csspin_python/aws_auth.py +195 -0
- csspin_python/aws_auth_schema.yaml +38 -0
- csspin_python/behave.py +150 -0
- csspin_python/behave_schema.yaml +39 -0
- csspin_python/debugpy.py +32 -0
- csspin_python/debugpy_schema.yaml +14 -0
- csspin_python/devpi.py +82 -0
- csspin_python/devpi_schema.yaml +17 -0
- csspin_python/playwright.py +126 -0
- csspin_python/playwright_schema.yaml +33 -0
- csspin_python/pytest.py +97 -0
- csspin_python/pytest_schema.yaml +27 -0
- csspin_python/python.py +970 -0
- csspin_python/python_schema.yaml +131 -0
- csspin_python/radon.py +59 -0
- csspin_python/radon_schema.yaml +17 -0
- csspin_python-2.0.0.dist-info/METADATA +109 -0
- csspin_python-2.0.0.dist-info/RECORD +21 -0
- csspin_python-2.0.0.dist-info/WHEEL +5 -0
- csspin_python-2.0.0.dist-info/licenses/LICENSE +176 -0
- csspin_python-2.0.0.dist-info/top_level.txt +1 -0
csspin_python/python.py
ADDED
|
@@ -0,0 +1,970 @@
|
|
|
1
|
+
# -*- mode: python; coding: utf-8 -*-
|
|
2
|
+
#
|
|
3
|
+
# Copyright (C) 2020 CONTACT Software GmbH
|
|
4
|
+
# https://www.contact-software.com/
|
|
5
|
+
#
|
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
|
+
# you may not use this file except in compliance with the License.
|
|
8
|
+
# You may obtain a copy of the License at
|
|
9
|
+
#
|
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
#
|
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
|
+
# See the License for the specific language governing permissions and
|
|
16
|
+
# limitations under the License.
|
|
17
|
+
#
|
|
18
|
+
# pylint: disable=too-few-public-methods,missing-class-docstring
|
|
19
|
+
|
|
20
|
+
"""``python``
|
|
21
|
+
==========
|
|
22
|
+
|
|
23
|
+
This plugin provisions the requested version of the Python
|
|
24
|
+
programming languages.
|
|
25
|
+
|
|
26
|
+
On Linux and macOS, Python is installed by compiling from source
|
|
27
|
+
(implying, that Python's build requirements must be installed). On
|
|
28
|
+
Windows, pre-built binaries are downloaded using `nuget`.
|
|
29
|
+
|
|
30
|
+
If a user has `pyenv <https://github.com/pyenv/pyenv>`_ installed it
|
|
31
|
+
can be activated by setting ``python.user_pyenv`` in
|
|
32
|
+
:file:`global.yaml`.
|
|
33
|
+
|
|
34
|
+
To skip provisioning of Python and use an already installed version,
|
|
35
|
+
:py:data:`python.use` can be set to the name or the full path of an
|
|
36
|
+
interpreter:
|
|
37
|
+
|
|
38
|
+
.. code-block:: console
|
|
39
|
+
|
|
40
|
+
spin -p python.use=/usr/local/bin/python ...
|
|
41
|
+
|
|
42
|
+
Note: `spin` will install or update certain packages of that
|
|
43
|
+
interpreter, thus write access is required.
|
|
44
|
+
|
|
45
|
+
Tasks
|
|
46
|
+
-----
|
|
47
|
+
|
|
48
|
+
.. click:: csspin_python:python
|
|
49
|
+
:prog: spin python
|
|
50
|
+
|
|
51
|
+
.. click:: csspin_python:python:wheel
|
|
52
|
+
:prog: spin python:wheel
|
|
53
|
+
|
|
54
|
+
.. click:: csspin_python:env
|
|
55
|
+
:prog: spin env
|
|
56
|
+
|
|
57
|
+
Properties
|
|
58
|
+
----------
|
|
59
|
+
|
|
60
|
+
* :py:data:`python.version` -- must be set to choose the
|
|
61
|
+
required Python version
|
|
62
|
+
* :py:data:`python.interpreter` -- path to the Python interpreter
|
|
63
|
+
|
|
64
|
+
Note: don't use these properties when using `virtualenv`, they will
|
|
65
|
+
point to the base installation.
|
|
66
|
+
|
|
67
|
+
"""
|
|
68
|
+
|
|
69
|
+
import abc
|
|
70
|
+
import configparser
|
|
71
|
+
import logging
|
|
72
|
+
import os
|
|
73
|
+
import re
|
|
74
|
+
import shutil
|
|
75
|
+
import sys
|
|
76
|
+
from subprocess import check_output
|
|
77
|
+
from textwrap import dedent, indent
|
|
78
|
+
|
|
79
|
+
from click.exceptions import Abort
|
|
80
|
+
from csspin import (
|
|
81
|
+
EXPORTS,
|
|
82
|
+
Command,
|
|
83
|
+
Memoizer,
|
|
84
|
+
Path,
|
|
85
|
+
Verbosity,
|
|
86
|
+
argument,
|
|
87
|
+
backtick,
|
|
88
|
+
cd,
|
|
89
|
+
config,
|
|
90
|
+
die,
|
|
91
|
+
download,
|
|
92
|
+
echo,
|
|
93
|
+
error,
|
|
94
|
+
exists,
|
|
95
|
+
get_requires,
|
|
96
|
+
info,
|
|
97
|
+
interpolate1,
|
|
98
|
+
memoizer,
|
|
99
|
+
mkdir,
|
|
100
|
+
namespaces,
|
|
101
|
+
normpath,
|
|
102
|
+
readtext,
|
|
103
|
+
rmtree,
|
|
104
|
+
setenv,
|
|
105
|
+
sh,
|
|
106
|
+
task,
|
|
107
|
+
warn,
|
|
108
|
+
writetext,
|
|
109
|
+
)
|
|
110
|
+
from csspin.tree import ConfigTree
|
|
111
|
+
|
|
112
|
+
defaults = config(
|
|
113
|
+
build_wheels=["{spin.project_root}"],
|
|
114
|
+
pyenv=config(
|
|
115
|
+
url="https://github.com/pyenv/pyenv.git",
|
|
116
|
+
path="{spin.data}/pyenv",
|
|
117
|
+
cache="{spin.data}/pyenv_cache",
|
|
118
|
+
python_build="{python.pyenv.path}/plugins/python-build/bin/python-build",
|
|
119
|
+
),
|
|
120
|
+
user_pyenv=False,
|
|
121
|
+
nuget=config(
|
|
122
|
+
url="https://dist.nuget.org/win-x86-commandline/latest/nuget.exe",
|
|
123
|
+
exe="{spin.data}/nuget.exe",
|
|
124
|
+
source="https://api.nuget.org/v3/index.json",
|
|
125
|
+
),
|
|
126
|
+
version=None,
|
|
127
|
+
use=None,
|
|
128
|
+
inst_dir=(
|
|
129
|
+
"{spin.data}/python/{python.version}"
|
|
130
|
+
if sys.platform != "win32"
|
|
131
|
+
else "{spin.data}/python/python.{python.version}/tools"
|
|
132
|
+
),
|
|
133
|
+
interpreter=(
|
|
134
|
+
"{python.inst_dir}/bin/python{platform.exe}"
|
|
135
|
+
if sys.platform != "win32"
|
|
136
|
+
else "{python.inst_dir}/python{platform.exe}"
|
|
137
|
+
),
|
|
138
|
+
venv="{spin.spin_dir}/venv",
|
|
139
|
+
memo="{python.venv}/spininfo.memo",
|
|
140
|
+
bindir="{python.venv}/bin" if sys.platform != "win32" else "{python.venv}",
|
|
141
|
+
scriptdir=(
|
|
142
|
+
"{python.venv}/bin" if sys.platform != "win32" else "{python.venv}/Scripts"
|
|
143
|
+
),
|
|
144
|
+
python="{python.scriptdir}/python{platform.exe}",
|
|
145
|
+
provisioner=None,
|
|
146
|
+
provisioner_memo="{spin.spin_dir}/python_provisioner.memo",
|
|
147
|
+
current_package=config(
|
|
148
|
+
install=True,
|
|
149
|
+
extras=[],
|
|
150
|
+
),
|
|
151
|
+
index_url="https://pypi.org/simple",
|
|
152
|
+
requires=config(
|
|
153
|
+
python=["build", "wheel"],
|
|
154
|
+
system=config(
|
|
155
|
+
debian=config(
|
|
156
|
+
apt=[
|
|
157
|
+
"build-essential",
|
|
158
|
+
"curl",
|
|
159
|
+
"git",
|
|
160
|
+
"libbz2-dev",
|
|
161
|
+
"libffi-dev",
|
|
162
|
+
"libkrb5-dev",
|
|
163
|
+
"liblzma-dev",
|
|
164
|
+
"libncursesw5-dev",
|
|
165
|
+
"libreadline-dev",
|
|
166
|
+
"libsqlite3-dev",
|
|
167
|
+
"libssl-dev",
|
|
168
|
+
"libxml2-dev",
|
|
169
|
+
"libxmlsec1-dev",
|
|
170
|
+
"make",
|
|
171
|
+
"xz-utils",
|
|
172
|
+
"zlib1g-dev",
|
|
173
|
+
]
|
|
174
|
+
)
|
|
175
|
+
),
|
|
176
|
+
),
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
@task()
|
|
181
|
+
def python(args):
|
|
182
|
+
"""Run the Python interpreter used for this projects."""
|
|
183
|
+
sh("python", *args)
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
@task("python:wheel", when="package")
|
|
187
|
+
def wheel(
|
|
188
|
+
cfg,
|
|
189
|
+
paths: argument(type=str, nargs=-1, required=False), # noqa: F722
|
|
190
|
+
):
|
|
191
|
+
"""Build a wheel of the current project and any additional wheels."""
|
|
192
|
+
setenv(PIP_INDEX_URL=cfg.python.index_url)
|
|
193
|
+
search_paths = paths or cfg.python.build_wheels
|
|
194
|
+
for build_path in {Path(path).absolute() for path in search_paths}:
|
|
195
|
+
try:
|
|
196
|
+
echo("Building PEP 517-like wheel")
|
|
197
|
+
sh(
|
|
198
|
+
"python",
|
|
199
|
+
"-m",
|
|
200
|
+
"build",
|
|
201
|
+
"-w",
|
|
202
|
+
build_path,
|
|
203
|
+
"-o",
|
|
204
|
+
"{spin.project_root}/dist",
|
|
205
|
+
)
|
|
206
|
+
except Abort:
|
|
207
|
+
echo("Building does not seem to work, use legacy setup.py style")
|
|
208
|
+
with cd(build_path):
|
|
209
|
+
sh(
|
|
210
|
+
"python",
|
|
211
|
+
"setup.py",
|
|
212
|
+
None if cfg.verbosity > Verbosity.NORMAL else "-v" "build",
|
|
213
|
+
"-b",
|
|
214
|
+
"{spin.project_root}/build",
|
|
215
|
+
"bdist_wheel",
|
|
216
|
+
"-d",
|
|
217
|
+
"{spin.project_root}/dist",
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
@task()
|
|
222
|
+
def env():
|
|
223
|
+
"""Generate command to activate the virtual environment"""
|
|
224
|
+
if sys.platform == "win32":
|
|
225
|
+
# Don't care about cmd
|
|
226
|
+
print(normpath("{python.scriptdir}", "activate.ps1"))
|
|
227
|
+
else:
|
|
228
|
+
print(f". {normpath('{python.scriptdir}', 'activate')}")
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def pyenv_install(cfg):
|
|
232
|
+
"""Install and setup the virtual environment using pyenv"""
|
|
233
|
+
with namespaces(cfg.python):
|
|
234
|
+
if cfg.python.user_pyenv:
|
|
235
|
+
info("Using your existing pyenv installation ...")
|
|
236
|
+
sh(f"pyenv install --skip-existing {cfg.python.version}")
|
|
237
|
+
cfg.python.interpreter = backtick("pyenv which python --nosystem").strip()
|
|
238
|
+
else:
|
|
239
|
+
info("Installing Python {version} to {inst_dir}")
|
|
240
|
+
# For Linux/macOS using the 'python-build' plugin from
|
|
241
|
+
# pyenv is by far the most robust way to install a
|
|
242
|
+
# version of Python.
|
|
243
|
+
if not exists("{pyenv.path}"):
|
|
244
|
+
sh(f"git clone {cfg.python.pyenv.url} {cfg.python.pyenv.path}")
|
|
245
|
+
else:
|
|
246
|
+
with cd(cfg.python.pyenv.path):
|
|
247
|
+
sh("git pull")
|
|
248
|
+
# we should set
|
|
249
|
+
setenv(PYTHON_BUILD_CACHE_PATH=mkdir(cfg.python.pyenv.cache))
|
|
250
|
+
setenv(PYTHON_CFLAGS="-DOPENSSL_NO_COMP")
|
|
251
|
+
try:
|
|
252
|
+
sh(
|
|
253
|
+
f"{cfg.python.pyenv.python_build} {cfg.python.version} {cfg.python.inst_dir}"
|
|
254
|
+
)
|
|
255
|
+
except Abort:
|
|
256
|
+
error("Failed to build the Python interpreter - removing it")
|
|
257
|
+
rmtree(cfg.python.inst_dir)
|
|
258
|
+
raise
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def nuget_install(cfg):
|
|
262
|
+
"""Install the virtual environment using nuget"""
|
|
263
|
+
if not exists(cfg.python.nuget.exe):
|
|
264
|
+
download(cfg.python.nuget.url, cfg.python.nuget.exe)
|
|
265
|
+
setenv(NUGET_HTTP_CACHE_PATH=cfg.spin.data / "nugetcache")
|
|
266
|
+
sh(
|
|
267
|
+
cfg.python.nuget.exe,
|
|
268
|
+
"install",
|
|
269
|
+
"-verbosity",
|
|
270
|
+
"quiet",
|
|
271
|
+
"-o",
|
|
272
|
+
cfg.spin.data / "python",
|
|
273
|
+
"python",
|
|
274
|
+
"-version",
|
|
275
|
+
cfg.python.version,
|
|
276
|
+
"-source",
|
|
277
|
+
cfg.python.nuget.source,
|
|
278
|
+
)
|
|
279
|
+
sh(f"{cfg.python.interpreter} -m ensurepip --upgrade")
|
|
280
|
+
sh(
|
|
281
|
+
cfg.python.interpreter,
|
|
282
|
+
"-mpip",
|
|
283
|
+
None if cfg.verbosity > Verbosity.NORMAL else "-q",
|
|
284
|
+
"install",
|
|
285
|
+
"-U",
|
|
286
|
+
"pip",
|
|
287
|
+
"wheel",
|
|
288
|
+
"packaging",
|
|
289
|
+
)
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
def provision(cfg: ConfigTree) -> None:
|
|
293
|
+
"""Provision the python plugin"""
|
|
294
|
+
with memoizer(cfg.python.provisioner_memo) as memo:
|
|
295
|
+
if cfg.python.provisioner is None:
|
|
296
|
+
cfg.python.provisioner = SimpleProvisioner()
|
|
297
|
+
if not memo.check(cfg.python.provisioner):
|
|
298
|
+
memo.add(cfg.python.provisioner)
|
|
299
|
+
|
|
300
|
+
info("Checking {python.interpreter}")
|
|
301
|
+
if not shutil.which(cfg.python.interpreter):
|
|
302
|
+
info("Provisioning '{python.interpreter}'")
|
|
303
|
+
cfg.python.provisioner.provision_python(cfg)
|
|
304
|
+
|
|
305
|
+
venv_provision(cfg)
|
|
306
|
+
|
|
307
|
+
cfg.python.site_packages = get_site_packages(interpreter=cfg.python.python)
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
def configure(cfg):
|
|
311
|
+
"""Configure the python plugin"""
|
|
312
|
+
if not cfg.python.version and not cfg.python.use:
|
|
313
|
+
die(
|
|
314
|
+
"Please choose a version in spinfile.yaml by setting python.version"
|
|
315
|
+
" or pass a local interpreter via python.use."
|
|
316
|
+
)
|
|
317
|
+
|
|
318
|
+
if cfg.python.use:
|
|
319
|
+
if cfg.python.version:
|
|
320
|
+
warn("python.version will be ignored, using '{python.use}' instead.")
|
|
321
|
+
cfg.python.interpreter = cfg.python.use
|
|
322
|
+
|
|
323
|
+
elif cfg.python.user_pyenv:
|
|
324
|
+
setenv(PYENV_VERSION="{python.version}")
|
|
325
|
+
try:
|
|
326
|
+
cfg.python.interpreter = backtick(
|
|
327
|
+
"pyenv which python --nosystem",
|
|
328
|
+
check=False,
|
|
329
|
+
silent=not cfg.verbosity > Verbosity.NORMAL,
|
|
330
|
+
).strip()
|
|
331
|
+
except Exception: # pylint: disable=broad-exception-caught # nosec
|
|
332
|
+
warn(
|
|
333
|
+
"The desired interpreter is not available within the"
|
|
334
|
+
" user's pyenv installation."
|
|
335
|
+
)
|
|
336
|
+
|
|
337
|
+
if exists(cfg.python.python):
|
|
338
|
+
cfg.python.site_packages = get_site_packages(interpreter=cfg.python.python)
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
def init(cfg):
|
|
342
|
+
"""Initialize the python plugin"""
|
|
343
|
+
if not cfg.python.use:
|
|
344
|
+
logging.debug("Checking for %s", cfg.python.interpreter)
|
|
345
|
+
if not exists(cfg.python.interpreter):
|
|
346
|
+
die(
|
|
347
|
+
f"Python {cfg.python.version} has not been provisioned for this"
|
|
348
|
+
" project. You might want to run spin with the 'provision'"
|
|
349
|
+
" task."
|
|
350
|
+
)
|
|
351
|
+
venv_init(cfg)
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
# We won't activate more than once.
|
|
355
|
+
ACTIVATED = False
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
def venv_init(cfg):
|
|
359
|
+
"""Activate the virtual environment"""
|
|
360
|
+
global ACTIVATED # pylint: disable=global-statement
|
|
361
|
+
if os.environ.get("VIRTUAL_ENV", "") != cfg.python.venv and not ACTIVATED:
|
|
362
|
+
activate_this = cfg.python.scriptdir / "activate_this.py"
|
|
363
|
+
if not exists(activate_this):
|
|
364
|
+
die(
|
|
365
|
+
f"{cfg.python.venv} does not exist. You may want to provision"
|
|
366
|
+
" it using 'spin provision'"
|
|
367
|
+
)
|
|
368
|
+
if sys.platform == "win32":
|
|
369
|
+
echo(f"{cfg.python.scriptdir}\\activate.ps1")
|
|
370
|
+
else:
|
|
371
|
+
echo(f". {cfg.python.scriptdir}/activate")
|
|
372
|
+
with open(activate_this, encoding="utf-8") as file:
|
|
373
|
+
exec( # pylint: disable=exec-used # nosec
|
|
374
|
+
file.read(), {"__file__": activate_this}
|
|
375
|
+
)
|
|
376
|
+
ACTIVATED = True
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
def patch_activate(schema):
|
|
380
|
+
"""Patch the activate script"""
|
|
381
|
+
if exists(schema.activatescript):
|
|
382
|
+
setters = []
|
|
383
|
+
resetters = set()
|
|
384
|
+
old_value_setters = set()
|
|
385
|
+
for name, value in EXPORTS:
|
|
386
|
+
value = schema.interpolate_environ_value(value)
|
|
387
|
+
setters.append(schema.setpattern.format(name=name, value=value))
|
|
388
|
+
resetters.add(schema.resetpattern.format(name=name))
|
|
389
|
+
old_value_setters.add(schema.old_env_pattern.format(name=name))
|
|
390
|
+
resetters = "\n".join(resetters)
|
|
391
|
+
setters = "\n".join(setters)
|
|
392
|
+
old_value_setters = "\n".join(old_value_setters)
|
|
393
|
+
original = readtext(schema.activatescript)
|
|
394
|
+
if schema.patchmarker not in original:
|
|
395
|
+
shutil.copyfile(
|
|
396
|
+
interpolate1(f"{schema.activatescript}"),
|
|
397
|
+
interpolate1(f"{schema.activatescript}.bak"),
|
|
398
|
+
)
|
|
399
|
+
info(f"Patching {schema.activatescript}")
|
|
400
|
+
# Removing the byte order marker (BOM) ensures the absence of those in
|
|
401
|
+
# the final scripts. BOMs in executables are not fully supported in
|
|
402
|
+
# Powershell.
|
|
403
|
+
original = (
|
|
404
|
+
readtext(f"{schema.activatescript}.bak").encode("utf-8").decode("utf-8-sig")
|
|
405
|
+
)
|
|
406
|
+
for repl in schema.replacements:
|
|
407
|
+
original = original.replace(repl[0], repl[1])
|
|
408
|
+
newscript = schema.script.format(
|
|
409
|
+
patchmarker=schema.patchmarker,
|
|
410
|
+
original=original,
|
|
411
|
+
resetters=resetters,
|
|
412
|
+
old_value_setters=old_value_setters,
|
|
413
|
+
setters=setters,
|
|
414
|
+
)
|
|
415
|
+
writetext(f"{schema.activatescript}", newscript)
|
|
416
|
+
|
|
417
|
+
|
|
418
|
+
class ActivateScriptPatcher(abc.ABC):
|
|
419
|
+
@staticmethod
|
|
420
|
+
@abc.abstractmethod
|
|
421
|
+
def interpolate_environ_value(value):
|
|
422
|
+
"""
|
|
423
|
+
Translate value so the script can handle uninterpolated "{ENVVAR}" literals in value
|
|
424
|
+
|
|
425
|
+
Example:
|
|
426
|
+
# Assume the following subset of os.environ
|
|
427
|
+
os.environ = {
|
|
428
|
+
"PATH": "/bin:/usr/bin",
|
|
429
|
+
"COMPILER_PATHS": "/compiler/A/bin:/compiler/B/bin",
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
# Now, setenv has been called with
|
|
433
|
+
# setenv(PATH="{python.scriptdir}:{COMPILER_PATHS}:{PATH}") thus the
|
|
434
|
+
# value of ``PATH`` in ``EXPORTS`` equals "/venv/bin:{COMPILER_PATHS}:{PATH}" as
|
|
435
|
+
# ``COMPILER_PATHS`` and ``PATH`` haven't been interpolated yet.
|
|
436
|
+
interpolate_environ_value(value) => /venv/bin:/compiler/A/bin:/compiler/B/bin:/bin:/usr/bin
|
|
437
|
+
"""
|
|
438
|
+
return value
|
|
439
|
+
|
|
440
|
+
|
|
441
|
+
class BashActivate(ActivateScriptPatcher):
|
|
442
|
+
patchmarker = "\n## Patched by csspin_python.python\n"
|
|
443
|
+
activatescript = Path("{python.scriptdir}") / "activate"
|
|
444
|
+
replacements = [
|
|
445
|
+
("deactivate", "origdeactivate"),
|
|
446
|
+
]
|
|
447
|
+
old_env_pattern = dedent(
|
|
448
|
+
"""
|
|
449
|
+
if [ -z ${{{name}+x}} ]; then
|
|
450
|
+
export _OLD_SPIN_UNSET{name}=""
|
|
451
|
+
else
|
|
452
|
+
export _OLD_SPIN_VALUE{name}="${name}"
|
|
453
|
+
fi
|
|
454
|
+
"""
|
|
455
|
+
)
|
|
456
|
+
setpattern = dedent(
|
|
457
|
+
"""
|
|
458
|
+
{name}="{value}"
|
|
459
|
+
export {name}
|
|
460
|
+
"""
|
|
461
|
+
)
|
|
462
|
+
resetpattern = indent(
|
|
463
|
+
dedent(
|
|
464
|
+
"""
|
|
465
|
+
if ! [ -z "${{_OLD_SPIN_VALUE{name}+_}}" ] ; then
|
|
466
|
+
{name}="$_OLD_SPIN_VALUE{name}"
|
|
467
|
+
export {name}
|
|
468
|
+
unset _OLD_SPIN_VALUE{name}
|
|
469
|
+
fi
|
|
470
|
+
if ! [ -z "${{_OLD_SPIN_UNSET{name}+_}}" ] ; then
|
|
471
|
+
unset {name}
|
|
472
|
+
unset _OLD_SPIN_UNSET{name}
|
|
473
|
+
fi
|
|
474
|
+
"""
|
|
475
|
+
),
|
|
476
|
+
prefix=" ",
|
|
477
|
+
)
|
|
478
|
+
script = dedent(
|
|
479
|
+
"""
|
|
480
|
+
{patchmarker}
|
|
481
|
+
{original}
|
|
482
|
+
deactivate () {{
|
|
483
|
+
{resetters}
|
|
484
|
+
if [ ! "${{1-}}" = "nondestructive" ] ; then
|
|
485
|
+
# Self destruct!
|
|
486
|
+
unset -f deactivate
|
|
487
|
+
origdeactivate
|
|
488
|
+
fi
|
|
489
|
+
}}
|
|
490
|
+
|
|
491
|
+
deactivate nondestructive
|
|
492
|
+
{old_value_setters}
|
|
493
|
+
{setters}
|
|
494
|
+
|
|
495
|
+
# The hash command must be called to get it to forget past
|
|
496
|
+
# commands. Without forgetting past commands the $PATH changes
|
|
497
|
+
# we made may not be respected
|
|
498
|
+
hash -r 2>/dev/null
|
|
499
|
+
"""
|
|
500
|
+
)
|
|
501
|
+
|
|
502
|
+
@staticmethod
|
|
503
|
+
def interpolate_environ_value(value):
|
|
504
|
+
if not value:
|
|
505
|
+
return ""
|
|
506
|
+
keys = re.findall(r"{(?P<key>\w+?)}", value)
|
|
507
|
+
for key in keys:
|
|
508
|
+
if key in os.environ:
|
|
509
|
+
value = value.replace(f"{{{key}}}", f"${key}")
|
|
510
|
+
return value
|
|
511
|
+
|
|
512
|
+
|
|
513
|
+
class PowershellActivate(ActivateScriptPatcher):
|
|
514
|
+
patchmarker = "\n## Patched by csspin_python.python\n"
|
|
515
|
+
activatescript = Path("{python.scriptdir}") / "activate.ps1"
|
|
516
|
+
replacements = [
|
|
517
|
+
("deactivate", "origdeactivate"),
|
|
518
|
+
]
|
|
519
|
+
old_env_pattern = (
|
|
520
|
+
"New-Variable -Scope global -Name _OLD_SPIN_{name} -Value $env:{name}"
|
|
521
|
+
)
|
|
522
|
+
setpattern = dedent(
|
|
523
|
+
"""
|
|
524
|
+
$env:{name} = "{value}"
|
|
525
|
+
"""
|
|
526
|
+
)
|
|
527
|
+
resetpattern = indent(
|
|
528
|
+
dedent(
|
|
529
|
+
"""
|
|
530
|
+
if (Test-Path variable:_OLD_SPIN_{name}) {{
|
|
531
|
+
$env:{name} = $variable:_OLD_SPIN_{name}
|
|
532
|
+
Remove-Variable "_OLD_SPIN_{name}" -Scope global
|
|
533
|
+
}}
|
|
534
|
+
"""
|
|
535
|
+
),
|
|
536
|
+
prefix=" ",
|
|
537
|
+
)
|
|
538
|
+
script = dedent(
|
|
539
|
+
"""
|
|
540
|
+
{patchmarker}
|
|
541
|
+
{original}
|
|
542
|
+
function global:deactivate([switch] $NonDestructive) {{
|
|
543
|
+
{resetters}
|
|
544
|
+
if (!$NonDestructive) {{
|
|
545
|
+
Remove-Item function:deactivate
|
|
546
|
+
origdeactivate
|
|
547
|
+
}}
|
|
548
|
+
}}
|
|
549
|
+
|
|
550
|
+
deactivate -nondestructive
|
|
551
|
+
{old_value_setters}
|
|
552
|
+
{setters}
|
|
553
|
+
"""
|
|
554
|
+
)
|
|
555
|
+
|
|
556
|
+
@staticmethod
|
|
557
|
+
def interpolate_environ_value(value):
|
|
558
|
+
if not value:
|
|
559
|
+
return ""
|
|
560
|
+
keys = re.findall(r"{(?P<key>\w+?)}", value)
|
|
561
|
+
for key in keys:
|
|
562
|
+
if key in os.environ:
|
|
563
|
+
value = value.replace(f"{{{key}}}", f"$env:{key}")
|
|
564
|
+
return value
|
|
565
|
+
|
|
566
|
+
|
|
567
|
+
class BatchActivate(ActivateScriptPatcher):
|
|
568
|
+
patchmarker = "\nREM Patched by csspin_python.python\n"
|
|
569
|
+
activatescript = Path("{python.scriptdir}") / "activate.bat"
|
|
570
|
+
replacements = ()
|
|
571
|
+
old_env_pattern = dedent(
|
|
572
|
+
"""
|
|
573
|
+
if defined _OLD_SPIN_VALUE_{name} goto ENDIFSPIN{name}1
|
|
574
|
+
if defined _OLD_SPIN_UNSET_{name} goto ENDIFSPIN{name}2
|
|
575
|
+
if defined {name} goto ENDIFSPIN{name}3
|
|
576
|
+
goto ENDIFSPIN{name}4
|
|
577
|
+
:ENDIFSPIN{name}1
|
|
578
|
+
set "{name}=%_OLD_SPIN_VALUE_{name}%"
|
|
579
|
+
set "_OLD_SPIN_VALUE_{name}=%{name}%"
|
|
580
|
+
goto ENDIFSPIN{name}5
|
|
581
|
+
:ENDIFSPIN{name}2
|
|
582
|
+
set "{name}="
|
|
583
|
+
set "_OLD_SPIN_UNSET_{name}= "
|
|
584
|
+
goto ENDIFSPIN{name}5
|
|
585
|
+
:ENDIFSPIN{name}3
|
|
586
|
+
set "_OLD_SPIN_VALUE_{name}=%{name}%"
|
|
587
|
+
goto ENDIFSPIN{name}5
|
|
588
|
+
:ENDIFSPIN{name}4
|
|
589
|
+
set "_OLD_SPIN_UNSET_{name}= "
|
|
590
|
+
goto ENDIFSPIN{name}5
|
|
591
|
+
:ENDIFSPIN{name}5
|
|
592
|
+
"""
|
|
593
|
+
)
|
|
594
|
+
setpattern = 'set "{name}={value}"'
|
|
595
|
+
resetpattern = ""
|
|
596
|
+
script = dedent(
|
|
597
|
+
"""
|
|
598
|
+
@echo off
|
|
599
|
+
{patchmarker}
|
|
600
|
+
{original}
|
|
601
|
+
{old_value_setters}
|
|
602
|
+
{setters}
|
|
603
|
+
"""
|
|
604
|
+
)
|
|
605
|
+
|
|
606
|
+
@staticmethod
|
|
607
|
+
def interpolate_environ_value(value):
|
|
608
|
+
if not value:
|
|
609
|
+
return ""
|
|
610
|
+
keys = re.findall(r"{(?P<key>\w+?)}", value)
|
|
611
|
+
for key in keys:
|
|
612
|
+
if key in os.environ:
|
|
613
|
+
value = value.replace(f"{{{key}}}", f"%{key}%")
|
|
614
|
+
return value
|
|
615
|
+
|
|
616
|
+
|
|
617
|
+
class BatchDeactivate(ActivateScriptPatcher):
|
|
618
|
+
patchmarker = "\nREM Patched by csspin_python.python\n"
|
|
619
|
+
activatescript = Path("{python.scriptdir}") / "deactivate.bat"
|
|
620
|
+
replacements = ()
|
|
621
|
+
old_env_pattern = ""
|
|
622
|
+
setpattern = ""
|
|
623
|
+
resetpattern = dedent(
|
|
624
|
+
"""
|
|
625
|
+
if defined _OLD_SPIN_VALUE_{name} goto ENDIFVSPIN{name}1
|
|
626
|
+
if defined _OLD_SPIN_UNSET_{name} goto ENDIFVSPIN{name}2
|
|
627
|
+
:ENDIFVSPIN{name}1
|
|
628
|
+
set "{name}=%_OLD_SPIN_VALUE_{name}%"
|
|
629
|
+
set _OLD_SPIN_VALUE_{name}=
|
|
630
|
+
goto ENDIFVSPIN{name}0
|
|
631
|
+
:ENDIFVSPIN{name}2
|
|
632
|
+
set {name}=
|
|
633
|
+
set _OLD_SPIN_UNSET_{name}=
|
|
634
|
+
goto ENDIFVSPIN{name}0
|
|
635
|
+
:ENDIFVSPIN{name}0
|
|
636
|
+
"""
|
|
637
|
+
)
|
|
638
|
+
script = dedent(
|
|
639
|
+
"""
|
|
640
|
+
@echo off
|
|
641
|
+
{patchmarker}
|
|
642
|
+
{original}
|
|
643
|
+
{resetters}
|
|
644
|
+
"""
|
|
645
|
+
)
|
|
646
|
+
|
|
647
|
+
|
|
648
|
+
class PythonActivate(ActivateScriptPatcher):
|
|
649
|
+
patchmarker = "# Patched by csspin_python.python\n"
|
|
650
|
+
activatescript = Path("{python.scriptdir}") / "activate_this.py"
|
|
651
|
+
replacements = ()
|
|
652
|
+
old_env_pattern = ""
|
|
653
|
+
setpattern = 'os.environ["{name}"] = fr"{value}"'
|
|
654
|
+
resetpattern = ""
|
|
655
|
+
script = dedent(
|
|
656
|
+
"""
|
|
657
|
+
{patchmarker}
|
|
658
|
+
{original}
|
|
659
|
+
{setters}
|
|
660
|
+
"""
|
|
661
|
+
)
|
|
662
|
+
|
|
663
|
+
@staticmethod
|
|
664
|
+
def interpolate_environ_value(value):
|
|
665
|
+
if not value:
|
|
666
|
+
return ""
|
|
667
|
+
keys = re.findall(r"{(?P<key>\w+?)}", value)
|
|
668
|
+
for key in keys:
|
|
669
|
+
if key in os.environ:
|
|
670
|
+
value = value.replace(f"{{{key}}}", f"{{os.environ['{key}']}}")
|
|
671
|
+
return value
|
|
672
|
+
|
|
673
|
+
|
|
674
|
+
def get_site_packages(interpreter):
|
|
675
|
+
"""Return the path to the virtual environments site-packages."""
|
|
676
|
+
return Path(
|
|
677
|
+
check_output(
|
|
678
|
+
[
|
|
679
|
+
interpolate1(interpreter),
|
|
680
|
+
"-c",
|
|
681
|
+
'import sysconfig; print(sysconfig.get_path("purelib"))',
|
|
682
|
+
],
|
|
683
|
+
)
|
|
684
|
+
.decode()
|
|
685
|
+
.strip(),
|
|
686
|
+
)
|
|
687
|
+
|
|
688
|
+
|
|
689
|
+
def finalize_provision(cfg):
|
|
690
|
+
"""Patching the activate scripts and preparing the site-packages"""
|
|
691
|
+
cfg.python.provisioner.install(cfg)
|
|
692
|
+
|
|
693
|
+
for schema in (
|
|
694
|
+
BashActivate,
|
|
695
|
+
BatchActivate,
|
|
696
|
+
BatchDeactivate,
|
|
697
|
+
PowershellActivate,
|
|
698
|
+
PythonActivate,
|
|
699
|
+
):
|
|
700
|
+
patch_activate(schema)
|
|
701
|
+
|
|
702
|
+
setenv_path = str(cfg.python.site_packages / "_set_env.pth")
|
|
703
|
+
info(f"Create {setenv_path}")
|
|
704
|
+
pthline = interpolate1(
|
|
705
|
+
"import os; "
|
|
706
|
+
"bindir=r'{python.bindir}'; "
|
|
707
|
+
"os.environ['PATH'] = "
|
|
708
|
+
"os.environ['PATH'] if bindir in os.environ['PATH'] "
|
|
709
|
+
"else os.pathsep.join((bindir, os.environ['PATH']))\n"
|
|
710
|
+
)
|
|
711
|
+
writetext(setenv_path, pthline)
|
|
712
|
+
|
|
713
|
+
|
|
714
|
+
class ProvisionerProtocol:
|
|
715
|
+
"""An implementation of this protocol is used to provision
|
|
716
|
+
dependencies to a virtual environment.
|
|
717
|
+
|
|
718
|
+
Separate plugins, can implement this interface and overwrite
|
|
719
|
+
cfg.python.provisioner.
|
|
720
|
+
|
|
721
|
+
.. note::
|
|
722
|
+
The provisioner will be memoized, so make sure it works with ``pickle.dumps``.
|
|
723
|
+
"""
|
|
724
|
+
|
|
725
|
+
# noinspection PyMethodMayBeStatic
|
|
726
|
+
def provision_python(self, cfg: ConfigTree) -> None:
|
|
727
|
+
"""Provision the project's python interpreter"""
|
|
728
|
+
if sys.platform == "win32":
|
|
729
|
+
nuget_install(cfg)
|
|
730
|
+
else:
|
|
731
|
+
# Everything else (Linux and macOS) uses pyenv
|
|
732
|
+
pyenv_install(cfg)
|
|
733
|
+
|
|
734
|
+
# noinspection PyMethodMayBeStatic
|
|
735
|
+
def provision_venv(self, cfg: ConfigTree) -> None:
|
|
736
|
+
"""Provision the virtual environment of the project"""
|
|
737
|
+
# virtualenv is guaranteed to be available like this
|
|
738
|
+
# as we declared it as one of spin's dependencies
|
|
739
|
+
cmd = [
|
|
740
|
+
sys.executable,
|
|
741
|
+
"-mvirtualenv",
|
|
742
|
+
None if cfg.verbosity > Verbosity.NORMAL else "-q",
|
|
743
|
+
]
|
|
744
|
+
virtualenv = Command(*cmd)
|
|
745
|
+
# do not download seeds, since we update pip later anyway
|
|
746
|
+
# add the plugins directory to the PYTHONPATH so that virtualenv will be found
|
|
747
|
+
virtualenv(
|
|
748
|
+
"-p",
|
|
749
|
+
cfg.python.interpreter,
|
|
750
|
+
cfg.python.venv,
|
|
751
|
+
env={"PYTHONPATH": cfg.spin.spin_dir / "plugins"},
|
|
752
|
+
)
|
|
753
|
+
|
|
754
|
+
def prerequisites(self, cfg: ConfigTree) -> None:
|
|
755
|
+
"""Provide requirements for the provisioning strategy."""
|
|
756
|
+
|
|
757
|
+
def lock(self, cfg: ConfigTree) -> None:
|
|
758
|
+
"""Lock the project's dependencies."""
|
|
759
|
+
|
|
760
|
+
def add(self, cfg: ConfigTree, req: str, devpackage: bool = False) -> None:
|
|
761
|
+
"""Add an extra dependency (incl. development ones)."""
|
|
762
|
+
|
|
763
|
+
def lock_extras(self, cfg: ConfigTree) -> None:
|
|
764
|
+
"""Lock the extra dependencies."""
|
|
765
|
+
|
|
766
|
+
def sync(self, cfg: ConfigTree) -> None:
|
|
767
|
+
"""Synchronize the environment with the locked dependencies."""
|
|
768
|
+
|
|
769
|
+
def install(self, cfg: ConfigTree) -> None:
|
|
770
|
+
"""Install the project itself."""
|
|
771
|
+
|
|
772
|
+
# noinspection PyMethodMayBeStatic
|
|
773
|
+
def cleanup(self, cfg: ConfigTree) -> None:
|
|
774
|
+
"""Cleanup the provisioned environment"""
|
|
775
|
+
rmtree(cfg.python.venv)
|
|
776
|
+
|
|
777
|
+
|
|
778
|
+
class SimpleProvisioner(ProvisionerProtocol):
|
|
779
|
+
"""The simplest Python dependency provisioner using pip.
|
|
780
|
+
|
|
781
|
+
This provisioner will never uninstall dependencies that are no
|
|
782
|
+
longer required.
|
|
783
|
+
"""
|
|
784
|
+
|
|
785
|
+
def __init__(self):
|
|
786
|
+
self.requirements = set()
|
|
787
|
+
self.devpackages = set()
|
|
788
|
+
self.m = Memoizer("{python.memo}")
|
|
789
|
+
|
|
790
|
+
def prerequisites(self, cfg):
|
|
791
|
+
# We'll need pip
|
|
792
|
+
sh(
|
|
793
|
+
"python",
|
|
794
|
+
"-mpip",
|
|
795
|
+
None if cfg.verbosity > Verbosity.NORMAL else "-q",
|
|
796
|
+
"--disable-pip-version-check",
|
|
797
|
+
"install",
|
|
798
|
+
"--index-url",
|
|
799
|
+
cfg.python.index_url,
|
|
800
|
+
"-U",
|
|
801
|
+
"pip",
|
|
802
|
+
)
|
|
803
|
+
|
|
804
|
+
def lock(self, cfg):
|
|
805
|
+
"""Noop"""
|
|
806
|
+
|
|
807
|
+
def add(self, cfg, req, devpackage=False):
|
|
808
|
+
# Add the requirement or devpackage if not already there.
|
|
809
|
+
if not self.m.check(req):
|
|
810
|
+
lst = self.devpackages if devpackage else self.requirements
|
|
811
|
+
lst.add(req)
|
|
812
|
+
|
|
813
|
+
def sync(self, cfg):
|
|
814
|
+
self.__execute_installation(
|
|
815
|
+
self.requirements,
|
|
816
|
+
None if cfg.verbosity > Verbosity.NORMAL else "-q",
|
|
817
|
+
cfg.python.index_url,
|
|
818
|
+
)
|
|
819
|
+
|
|
820
|
+
def install(self, cfg):
|
|
821
|
+
quietflag = None if cfg.verbosity > Verbosity.NORMAL else "-q"
|
|
822
|
+
self.__execute_installation(self.devpackages, quietflag, cfg.python.index_url)
|
|
823
|
+
|
|
824
|
+
# If there is a setup.py, make an editable install (which
|
|
825
|
+
# transitively also installs runtime dependencies of the project).
|
|
826
|
+
if cfg.python.current_package.install and any(
|
|
827
|
+
(exists("setup.py"), exists("setup.cfg"), exists("pyproject.toml"))
|
|
828
|
+
):
|
|
829
|
+
cmd = [
|
|
830
|
+
"pip",
|
|
831
|
+
quietflag,
|
|
832
|
+
"--disable-pip-version-check",
|
|
833
|
+
"install",
|
|
834
|
+
"--index-url",
|
|
835
|
+
cfg.python.index_url,
|
|
836
|
+
"-e",
|
|
837
|
+
]
|
|
838
|
+
if cfg.python.current_package.extras:
|
|
839
|
+
cmd.append(f".[{','.join(cfg.python.current_package.extras)}]")
|
|
840
|
+
else:
|
|
841
|
+
cmd.append(".")
|
|
842
|
+
sh(*cmd)
|
|
843
|
+
|
|
844
|
+
# Verify dependency compatibility of installed packages
|
|
845
|
+
pip_check = sh(
|
|
846
|
+
"pip",
|
|
847
|
+
"--disable-pip-version-check",
|
|
848
|
+
"check",
|
|
849
|
+
check=False,
|
|
850
|
+
capture_output=True,
|
|
851
|
+
)
|
|
852
|
+
if pip_check.returncode:
|
|
853
|
+
die(pip_check.stdout)
|
|
854
|
+
|
|
855
|
+
def _split(self, reqset):
|
|
856
|
+
"""to pass whitespace-less args to sh()"""
|
|
857
|
+
reqlist = []
|
|
858
|
+
for req in reqset:
|
|
859
|
+
reqlist.extend(req.split())
|
|
860
|
+
return reqlist
|
|
861
|
+
|
|
862
|
+
def __execute_installation(self, packages, quietflag, index_url):
|
|
863
|
+
"""Install packages that are not yet memoized"""
|
|
864
|
+
if to_install := {package for package in packages if not self.m.check(package)}:
|
|
865
|
+
sh(
|
|
866
|
+
"pip",
|
|
867
|
+
quietflag,
|
|
868
|
+
"--disable-pip-version-check",
|
|
869
|
+
"install",
|
|
870
|
+
"--index-url",
|
|
871
|
+
index_url,
|
|
872
|
+
*self._split(to_install),
|
|
873
|
+
)
|
|
874
|
+
for package in to_install:
|
|
875
|
+
self.m.add(package)
|
|
876
|
+
self.m.save()
|
|
877
|
+
|
|
878
|
+
|
|
879
|
+
def venv_provision(cfg): # pylint: disable=too-many-branches,missing-function-docstring
|
|
880
|
+
fresh_env = False
|
|
881
|
+
|
|
882
|
+
info("Checking venv '{python.venv}'")
|
|
883
|
+
if not exists(cfg.python.venv):
|
|
884
|
+
info("Provisioning venv '{python.venv}'")
|
|
885
|
+
cfg.python.provisioner.provision_venv(cfg)
|
|
886
|
+
fresh_env = True
|
|
887
|
+
|
|
888
|
+
# This sets PATH to the venv
|
|
889
|
+
init(cfg)
|
|
890
|
+
|
|
891
|
+
# Always create a pip conf with at least the packageserver index_url as content
|
|
892
|
+
if sys.platform == "win32":
|
|
893
|
+
pipconf = cfg.python.venv / "pip.ini"
|
|
894
|
+
else:
|
|
895
|
+
pipconf = cfg.python.venv / "pip.conf"
|
|
896
|
+
|
|
897
|
+
_create_pipconf(cfg, pipconf)
|
|
898
|
+
|
|
899
|
+
# Establish the prerequisites
|
|
900
|
+
if fresh_env:
|
|
901
|
+
cfg.python.provisioner.prerequisites(cfg)
|
|
902
|
+
|
|
903
|
+
# Plugins can define a 'venv_hook' function, to give them a
|
|
904
|
+
# chance to do something with the virtual environment just
|
|
905
|
+
# being provisioned (e.g. preparing the venv by adding pth
|
|
906
|
+
# files or by adding packages with other installers like
|
|
907
|
+
# easy_install).
|
|
908
|
+
for plugin in cfg.spin.topo_plugins:
|
|
909
|
+
plugin_module = cfg.loaded[plugin]
|
|
910
|
+
hook = getattr(plugin_module, "venv_hook", None)
|
|
911
|
+
if hook is not None:
|
|
912
|
+
logging.debug(f"{plugin_module.__name__}.venv_hook()")
|
|
913
|
+
hook(cfg)
|
|
914
|
+
|
|
915
|
+
cfg.python.provisioner.lock(cfg)
|
|
916
|
+
|
|
917
|
+
# Install packages required by the project ('requirements')
|
|
918
|
+
for req in cfg.python.get("requirements", []):
|
|
919
|
+
cfg.python.provisioner.add(cfg, interpolate1(req))
|
|
920
|
+
|
|
921
|
+
# Install development packages required by the project ('devpackages')
|
|
922
|
+
for pkgspec in cfg.python.get("devpackages", []):
|
|
923
|
+
cfg.python.provisioner.add(cfg, interpolate1(pkgspec), True)
|
|
924
|
+
|
|
925
|
+
# Install packages required by plugins used
|
|
926
|
+
# ('<plugin>.requires.python')
|
|
927
|
+
for plugin in cfg.spin.topo_plugins:
|
|
928
|
+
plugin_module = cfg.loaded[plugin]
|
|
929
|
+
for req in get_requires(plugin_module.defaults, "python"):
|
|
930
|
+
cfg.python.provisioner.add(cfg, interpolate1(req))
|
|
931
|
+
|
|
932
|
+
cfg.python.provisioner.lock_extras(cfg)
|
|
933
|
+
cfg.python.provisioner.sync(cfg)
|
|
934
|
+
|
|
935
|
+
|
|
936
|
+
def cleanup(cfg: ConfigTree) -> None:
|
|
937
|
+
"""Remove directories and files generated by the python plugin."""
|
|
938
|
+
with memoizer(cfg.python.provisioner_memo) as memo:
|
|
939
|
+
for provisioner in memo.items():
|
|
940
|
+
try:
|
|
941
|
+
provisioner.cleanup(cfg)
|
|
942
|
+
except Exception as err: # pylint: disable=broad-exception-caught
|
|
943
|
+
warn(
|
|
944
|
+
"Cleaning up the python environment of provisioner class "
|
|
945
|
+
f"'{provisioner.__class__.__name__}' failed: {err}"
|
|
946
|
+
)
|
|
947
|
+
memo.clear()
|
|
948
|
+
rmtree(cfg.python.provisioner_memo)
|
|
949
|
+
for path in cfg.python.build_wheels:
|
|
950
|
+
current_path = Path(interpolate1(path))
|
|
951
|
+
rmtree(current_path / "build")
|
|
952
|
+
rmtree(current_path / "dist")
|
|
953
|
+
for filename in os.listdir(current_path):
|
|
954
|
+
if filename.endswith(".egg-info") or filename.endswith(".dist-info"):
|
|
955
|
+
rmtree(current_path / filename)
|
|
956
|
+
|
|
957
|
+
|
|
958
|
+
def _create_pipconf(cfg, configfile):
|
|
959
|
+
# pip cannot handle a section being defined twice, so let's insert the
|
|
960
|
+
# index_url ourselves and create the configfile
|
|
961
|
+
config_parser = configparser.ConfigParser()
|
|
962
|
+
config_parser.read_string(cfg.python.pipconf)
|
|
963
|
+
if not config_parser.has_section("global"):
|
|
964
|
+
config_parser.add_section("global")
|
|
965
|
+
if not (
|
|
966
|
+
"index_url" in config_parser["global"] or "index-url" in config_parser["global"]
|
|
967
|
+
):
|
|
968
|
+
config_parser["global"]["index_url"] = cfg.python.index_url
|
|
969
|
+
with open(configfile, mode="w", encoding="utf-8") as fd:
|
|
970
|
+
config_parser.write(fd)
|