pex 2.59.5__py2.py3-none-any.whl → 2.60.0__py2.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.
Potentially problematic release.
This version of pex might be problematic. Click here for more details.
- pex/cache/dirs.py +14 -4
- pex/cli/commands/lock.py +8 -5
- pex/common.py +57 -7
- pex/dist_metadata.py +48 -6
- pex/docs/html/_pagefind/fragment/en_39c0488.pf_fragment +0 -0
- pex/docs/html/_pagefind/fragment/en_3eeaaf4.pf_fragment +0 -0
- pex/docs/html/_pagefind/fragment/en_a1dde36.pf_fragment +0 -0
- pex/docs/html/_pagefind/fragment/en_a755644.pf_fragment +0 -0
- pex/docs/html/_pagefind/fragment/en_b16e3bd.pf_fragment +0 -0
- pex/docs/html/_pagefind/fragment/{en_ecf679c.pf_fragment → en_c5d35a7.pf_fragment} +0 -0
- pex/docs/html/_pagefind/fragment/en_ec62bd2.pf_fragment +0 -0
- pex/docs/html/_pagefind/fragment/en_f32628f.pf_fragment +0 -0
- pex/docs/html/_pagefind/index/{en_974dc5a.pf_index → en_b211695.pf_index} +0 -0
- pex/docs/html/_pagefind/pagefind-entry.json +1 -1
- pex/docs/html/_pagefind/pagefind.en_e8a49380e5.pf_meta +0 -0
- pex/docs/html/_static/documentation_options.js +1 -1
- pex/docs/html/api/vars.html +5 -5
- pex/docs/html/buildingpex.html +5 -5
- pex/docs/html/genindex.html +5 -5
- pex/docs/html/index.html +5 -5
- pex/docs/html/recipes.html +5 -5
- pex/docs/html/scie.html +5 -5
- pex/docs/html/search.html +5 -5
- pex/docs/html/whatispex.html +5 -5
- pex/entry_points_txt.py +98 -0
- pex/environment.py +13 -10
- pex/finders.py +1 -1
- pex/installed_wheel.py +127 -0
- pex/interpreter.py +17 -5
- pex/interpreter_constraints.py +4 -4
- pex/pep_376.py +20 -380
- pex/pep_427.py +736 -248
- pex/pex_builder.py +4 -4
- pex/pex_info.py +8 -3
- pex/resolve/venv_resolver.py +46 -25
- pex/resolver.py +10 -3
- pex/sysconfig.py +5 -3
- pex/third_party/__init__.py +1 -1
- pex/tools/commands/repository.py +47 -24
- pex/vendor/__init__.py +1 -8
- pex/vendor/__main__.py +62 -41
- pex/vendor/_vendored/ansicolors/.layout.json +1 -1
- pex/vendor/_vendored/ansicolors/ansicolors-1.1.8.dist-info/RECORD +11 -0
- pex/vendor/_vendored/ansicolors/ansicolors-1.1.8.pex-info/original-whl-info.json +1 -0
- pex/vendor/_vendored/appdirs/.layout.json +1 -1
- pex/vendor/_vendored/appdirs/appdirs-1.4.4.dist-info/RECORD +7 -0
- pex/vendor/_vendored/appdirs/appdirs-1.4.4.pex-info/original-whl-info.json +1 -0
- pex/vendor/_vendored/attrs/.layout.json +1 -1
- pex/vendor/_vendored/attrs/attrs-21.5.0.dev0.dist-info/RECORD +37 -0
- pex/vendor/_vendored/attrs/attrs-21.5.0.dev0.pex-info/original-whl-info.json +1 -0
- pex/vendor/_vendored/packaging_20_9/.layout.json +1 -1
- pex/vendor/_vendored/packaging_20_9/packaging-20.9.dist-info/RECORD +20 -0
- pex/vendor/_vendored/packaging_20_9/packaging-20.9.pex-info/original-whl-info.json +1 -0
- pex/vendor/_vendored/packaging_20_9/pyparsing-2.4.7.dist-info/RECORD +7 -0
- pex/vendor/_vendored/packaging_20_9/pyparsing-2.4.7.pex-info/original-whl-info.json +1 -0
- pex/vendor/_vendored/packaging_21_3/.layout.json +1 -1
- pex/vendor/_vendored/packaging_21_3/packaging-21.3.dist-info/RECORD +20 -0
- pex/vendor/_vendored/packaging_21_3/packaging-21.3.pex-info/original-whl-info.json +1 -0
- pex/vendor/_vendored/packaging_21_3/pyparsing-3.0.7.dist-info/RECORD +18 -0
- pex/vendor/_vendored/packaging_21_3/pyparsing-3.0.7.pex-info/original-whl-info.json +1 -0
- pex/vendor/_vendored/packaging_24_0/.layout.json +1 -1
- pex/vendor/_vendored/packaging_24_0/packaging-24.0.dist-info/RECORD +22 -0
- pex/vendor/_vendored/packaging_24_0/packaging-24.0.pex-info/original-whl-info.json +1 -0
- pex/vendor/_vendored/packaging_25_0/.layout.json +1 -1
- pex/vendor/_vendored/packaging_25_0/packaging-25.0.dist-info/RECORD +24 -0
- pex/vendor/_vendored/packaging_25_0/packaging-25.0.pex-info/original-whl-info.json +1 -0
- pex/vendor/_vendored/pip/.layout.json +1 -1
- pex/vendor/_vendored/pip/pip-20.3.4.dist-info/RECORD +388 -0
- pex/vendor/_vendored/pip/pip-20.3.4.pex-info/original-whl-info.json +1 -0
- pex/vendor/_vendored/setuptools/.layout.json +1 -1
- pex/vendor/_vendored/setuptools/setuptools-44.0.0+3acb925dd708430aeaf197ea53ac8a752f7c1863.dist-info/RECORD +107 -0
- pex/vendor/_vendored/setuptools/setuptools-44.0.0+3acb925dd708430aeaf197ea53ac8a752f7c1863.pex-info/original-whl-info.json +1 -0
- pex/vendor/_vendored/toml/.layout.json +1 -1
- pex/vendor/_vendored/toml/toml-0.10.2.dist-info/RECORD +11 -0
- pex/vendor/_vendored/toml/toml-0.10.2.pex-info/original-whl-info.json +1 -0
- pex/vendor/_vendored/tomli/.layout.json +1 -1
- pex/vendor/_vendored/tomli/tomli-2.0.1.dist-info/RECORD +10 -0
- pex/vendor/_vendored/tomli/tomli-2.0.1.pex-info/original-whl-info.json +1 -0
- pex/venv/installer.py +9 -5
- pex/version.py +1 -1
- pex/wheel.py +79 -15
- pex/whl.py +67 -0
- pex/windows/__init__.py +14 -11
- {pex-2.59.5.dist-info → pex-2.60.0.dist-info}/METADATA +4 -4
- {pex-2.59.5.dist-info → pex-2.60.0.dist-info}/RECORD +90 -74
- pex/docs/html/_pagefind/fragment/en_34b3bf8.pf_fragment +0 -0
- pex/docs/html/_pagefind/fragment/en_3cefc8e.pf_fragment +0 -0
- pex/docs/html/_pagefind/fragment/en_44ba8a7.pf_fragment +0 -0
- pex/docs/html/_pagefind/fragment/en_8eb9a56.pf_fragment +0 -0
- pex/docs/html/_pagefind/fragment/en_db171fd.pf_fragment +0 -0
- pex/docs/html/_pagefind/fragment/en_fb971c7.pf_fragment +0 -0
- pex/docs/html/_pagefind/fragment/en_fd8f242.pf_fragment +0 -0
- pex/docs/html/_pagefind/pagefind.en_3549188bce.pf_meta +0 -0
- pex/vendor/_vendored/ansicolors/ansicolors-1.1.8.dist-info/INSTALLER +0 -1
- pex/vendor/_vendored/appdirs/appdirs-1.4.4.dist-info/INSTALLER +0 -1
- pex/vendor/_vendored/attrs/attrs-21.5.0.dev0.dist-info/INSTALLER +0 -1
- pex/vendor/_vendored/packaging_20_9/packaging-20.9.dist-info/INSTALLER +0 -1
- pex/vendor/_vendored/packaging_20_9/pyparsing-2.4.7.dist-info/INSTALLER +0 -1
- pex/vendor/_vendored/packaging_21_3/packaging-21.3.dist-info/INSTALLER +0 -1
- pex/vendor/_vendored/packaging_21_3/pyparsing-3.0.7.dist-info/INSTALLER +0 -1
- pex/vendor/_vendored/packaging_24_0/packaging-24.0.dist-info/INSTALLER +0 -1
- pex/vendor/_vendored/packaging_25_0/packaging-25.0.dist-info/INSTALLER +0 -1
- pex/vendor/_vendored/pip/pip-20.3.4.dist-info/INSTALLER +0 -1
- pex/vendor/_vendored/setuptools/setuptools-44.0.0+3acb925dd708430aeaf197ea53ac8a752f7c1863.dist-info/INSTALLER +0 -1
- pex/vendor/_vendored/toml/toml-0.10.2.dist-info/INSTALLER +0 -1
- pex/vendor/_vendored/tomli/tomli-2.0.1.dist-info/INSTALLER +0 -1
- {pex-2.59.5.dist-info → pex-2.60.0.dist-info}/WHEEL +0 -0
- {pex-2.59.5.dist-info → pex-2.60.0.dist-info}/entry_points.txt +0 -0
- {pex-2.59.5.dist-info → pex-2.60.0.dist-info}/licenses/LICENSE +0 -0
- {pex-2.59.5.dist-info → pex-2.60.0.dist-info}/pylock/pylock.toml +0 -0
- {pex-2.59.5.dist-info → pex-2.60.0.dist-info}/top_level.txt +0 -0
pex/pep_376.py
CHANGED
|
@@ -5,33 +5,17 @@ from __future__ import absolute_import
|
|
|
5
5
|
|
|
6
6
|
import base64
|
|
7
7
|
import csv
|
|
8
|
-
import errno
|
|
9
8
|
import hashlib
|
|
10
|
-
import itertools
|
|
11
|
-
import json
|
|
12
9
|
import os
|
|
13
|
-
import shutil
|
|
14
10
|
from fileinput import FileInput
|
|
15
11
|
|
|
16
12
|
from pex import hashing
|
|
17
|
-
from pex.common import
|
|
18
|
-
CopyMode,
|
|
19
|
-
is_pyc_dir,
|
|
20
|
-
is_pyc_file,
|
|
21
|
-
safe_mkdir,
|
|
22
|
-
safe_open,
|
|
23
|
-
safe_relative_symlink,
|
|
24
|
-
)
|
|
13
|
+
from pex.common import safe_open
|
|
25
14
|
from pex.compatibility import PY2
|
|
26
|
-
from pex.
|
|
27
|
-
from pex.interpreter import PythonInterpreter
|
|
28
|
-
from pex.typing import TYPE_CHECKING, cast
|
|
29
|
-
from pex.util import CacheHelper
|
|
30
|
-
from pex.venv.virtualenv import Virtualenv
|
|
31
|
-
from pex.wheel import WHEEL, Wheel, WheelMetadataLoadError
|
|
15
|
+
from pex.typing import TYPE_CHECKING
|
|
32
16
|
|
|
33
17
|
if TYPE_CHECKING:
|
|
34
|
-
from typing import IO, Callable, Iterable, Iterator, Optional, Protocol, Text,
|
|
18
|
+
from typing import IO, Callable, Iterable, Iterator, Optional, Protocol, Text, Union
|
|
35
19
|
|
|
36
20
|
import attr # vendor:skip
|
|
37
21
|
|
|
@@ -81,39 +65,6 @@ class Hash(object):
|
|
|
81
65
|
return self.value
|
|
82
66
|
|
|
83
67
|
|
|
84
|
-
def find_and_replace_path_components(
|
|
85
|
-
path, # type: Text
|
|
86
|
-
find, # type: str
|
|
87
|
-
replace, # type: str
|
|
88
|
-
):
|
|
89
|
-
# type: (...) -> Text
|
|
90
|
-
"""Replace components of `path` that are exactly `find` with `replace`.
|
|
91
|
-
|
|
92
|
-
>>> find_and_replace_path_components("foo/bar/baz", "bar", "spam")
|
|
93
|
-
foo/spam/baz
|
|
94
|
-
>>>
|
|
95
|
-
"""
|
|
96
|
-
if not find or not replace:
|
|
97
|
-
raise ValueError(
|
|
98
|
-
"Both find and replace must be non-empty strings. Given find={find!r} "
|
|
99
|
-
"replace={replace!r}".format(find=find, replace=replace)
|
|
100
|
-
)
|
|
101
|
-
if not path:
|
|
102
|
-
return path
|
|
103
|
-
|
|
104
|
-
components = []
|
|
105
|
-
head = path
|
|
106
|
-
while head:
|
|
107
|
-
new_head, tail = os.path.split(head)
|
|
108
|
-
if new_head == head:
|
|
109
|
-
components.append(head)
|
|
110
|
-
break
|
|
111
|
-
components.append(tail)
|
|
112
|
-
head = new_head
|
|
113
|
-
components.reverse()
|
|
114
|
-
return os.path.join(*(replace if component == find else component for component in components))
|
|
115
|
-
|
|
116
|
-
|
|
117
68
|
@attr.s(frozen=True)
|
|
118
69
|
class InstalledFile(object):
|
|
119
70
|
"""The record of a single installed file from a PEP 376 RECORD file.
|
|
@@ -121,336 +72,23 @@ class InstalledFile(object):
|
|
|
121
72
|
See: https://peps.python.org/pep-0376/#record
|
|
122
73
|
"""
|
|
123
74
|
|
|
124
|
-
_PYTHON_VER_PLACEHOLDER = "pythonX.Y"
|
|
125
|
-
|
|
126
|
-
@staticmethod
|
|
127
|
-
def _python_ver(interpreter=None):
|
|
128
|
-
# type: (Optional[PythonInterpreter]) -> str
|
|
129
|
-
python = interpreter or PythonInterpreter.get()
|
|
130
|
-
return "python{major}.{minor}".format(major=python.version[0], minor=python.version[1])
|
|
131
|
-
|
|
132
|
-
@classmethod
|
|
133
|
-
def normalized_path(
|
|
134
|
-
cls,
|
|
135
|
-
path, # type: Text
|
|
136
|
-
interpreter=None, # type: Optional[PythonInterpreter]
|
|
137
|
-
):
|
|
138
|
-
# type: (...) -> Text
|
|
139
|
-
return find_and_replace_path_components(
|
|
140
|
-
path, cls._python_ver(interpreter=interpreter), cls._PYTHON_VER_PLACEHOLDER
|
|
141
|
-
)
|
|
142
|
-
|
|
143
|
-
@classmethod
|
|
144
|
-
def denormalized_path(
|
|
145
|
-
cls,
|
|
146
|
-
path, # type: str
|
|
147
|
-
interpreter=None, # type: Optional[PythonInterpreter]
|
|
148
|
-
):
|
|
149
|
-
# type: (...) -> Text
|
|
150
|
-
return find_and_replace_path_components(
|
|
151
|
-
path, cls._PYTHON_VER_PLACEHOLDER, cls._python_ver(interpreter=interpreter)
|
|
152
|
-
)
|
|
153
|
-
|
|
154
75
|
path = attr.ib() # type: Text
|
|
155
76
|
hash = attr.ib(default=None) # type: Optional[Hash]
|
|
156
77
|
size = attr.ib(default=None) # type: Optional[int]
|
|
157
78
|
|
|
158
79
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
@classmethod
|
|
172
|
-
def save(
|
|
173
|
-
cls,
|
|
174
|
-
prefix_dir, # type: str
|
|
175
|
-
stash_dir, # type: str
|
|
176
|
-
record_relpath, # type: Text
|
|
177
|
-
root_is_purelib, # type: bool
|
|
178
|
-
):
|
|
179
|
-
# type: (...) -> InstalledWheel
|
|
180
|
-
|
|
181
|
-
# We currently need the installed wheel chroot hash for PEX-INFO / boot purposes. It is
|
|
182
|
-
# expensive to calculate; so we do it here 1 time when saving the installed wheel.
|
|
183
|
-
fingerprint = CacheHelper.dir_hash(prefix_dir, hasher=hashlib.sha256)
|
|
184
|
-
|
|
185
|
-
layout = {
|
|
186
|
-
"stash_dir": stash_dir,
|
|
187
|
-
"record_relpath": record_relpath,
|
|
188
|
-
"fingerprint": fingerprint,
|
|
189
|
-
"root_is_purelib": root_is_purelib,
|
|
190
|
-
}
|
|
191
|
-
with open(cls.layout_file(prefix_dir), "w") as fp:
|
|
192
|
-
json.dump(layout, fp, sort_keys=True)
|
|
193
|
-
return cls(
|
|
194
|
-
prefix_dir=prefix_dir,
|
|
195
|
-
stash_dir=stash_dir,
|
|
196
|
-
record_relpath=record_relpath,
|
|
197
|
-
fingerprint=fingerprint,
|
|
198
|
-
root_is_purelib=root_is_purelib,
|
|
199
|
-
)
|
|
200
|
-
|
|
201
|
-
@classmethod
|
|
202
|
-
def load(cls, prefix_dir):
|
|
203
|
-
# type: (str) -> InstalledWheel
|
|
204
|
-
layout_file = cls.layout_file(prefix_dir)
|
|
205
|
-
try:
|
|
206
|
-
with open(layout_file) as fp:
|
|
207
|
-
layout = json.load(fp)
|
|
208
|
-
except (IOError, OSError) as e:
|
|
209
|
-
raise cls.LoadError(
|
|
210
|
-
"Failed to load an installed wheel layout from {layout_file}: {err}".format(
|
|
211
|
-
layout_file=layout_file, err=e
|
|
212
|
-
)
|
|
213
|
-
)
|
|
214
|
-
if not isinstance(layout, dict):
|
|
215
|
-
raise cls.LoadError(
|
|
216
|
-
"The installed wheel layout file at {layout_file} must contain a single top-level "
|
|
217
|
-
"object, found: {value}.".format(layout_file=layout_file, value=layout)
|
|
218
|
-
)
|
|
219
|
-
stash_dir = layout.get("stash_dir")
|
|
220
|
-
record_relpath = layout.get("record_relpath")
|
|
221
|
-
if not stash_dir or not record_relpath:
|
|
222
|
-
raise cls.LoadError(
|
|
223
|
-
"The installed wheel layout file at {layout_file} must contain an object with both "
|
|
224
|
-
"`stash_dir` and `record_relpath` attributes, found: {value}".format(
|
|
225
|
-
layout_file=layout_file, value=layout
|
|
226
|
-
)
|
|
227
|
-
)
|
|
228
|
-
|
|
229
|
-
fingerprint = layout.get("fingerprint")
|
|
230
|
-
|
|
231
|
-
# N.B.: Caching root_is_purelib was not part of the original InstalledWheel layout data; so
|
|
232
|
-
# we materialize the property if needed to support older installed wheel chroots.
|
|
233
|
-
root_is_purelib = layout.get("root_is_purelib")
|
|
234
|
-
if root_is_purelib is None:
|
|
235
|
-
try:
|
|
236
|
-
wheel = WHEEL.load(prefix_dir)
|
|
237
|
-
except WheelMetadataLoadError as e:
|
|
238
|
-
raise cls.LoadError(
|
|
239
|
-
"Failed to determine if installed wheel at {location} is platform-specific: "
|
|
240
|
-
"{err}".format(location=prefix_dir, err=e)
|
|
241
|
-
)
|
|
242
|
-
root_is_purelib = wheel.root_is_purelib
|
|
243
|
-
|
|
244
|
-
return cls(
|
|
245
|
-
prefix_dir=prefix_dir,
|
|
246
|
-
stash_dir=cast(str, stash_dir),
|
|
247
|
-
record_relpath=cast(str, record_relpath),
|
|
248
|
-
fingerprint=cast("Optional[str]", fingerprint),
|
|
249
|
-
root_is_purelib=root_is_purelib,
|
|
250
|
-
)
|
|
251
|
-
|
|
252
|
-
prefix_dir = attr.ib() # type: str
|
|
253
|
-
stash_dir = attr.ib() # type: str
|
|
254
|
-
record_relpath = attr.ib() # type: Text
|
|
255
|
-
fingerprint = attr.ib() # type: Optional[str]
|
|
256
|
-
root_is_purelib = attr.ib() # type: bool
|
|
257
|
-
|
|
258
|
-
def wheel_file_name(self):
|
|
259
|
-
# type: () -> str
|
|
260
|
-
return Wheel.load(self.prefix_dir).wheel_file_name
|
|
261
|
-
|
|
262
|
-
def stashed_path(self, *components):
|
|
263
|
-
# type: (*str) -> str
|
|
264
|
-
return os.path.join(self.prefix_dir, self.stash_dir, *components)
|
|
265
|
-
|
|
266
|
-
@staticmethod
|
|
267
|
-
def create_installed_file(
|
|
268
|
-
path, # type: Text
|
|
269
|
-
dest_dir, # type: str
|
|
270
|
-
):
|
|
271
|
-
# type: (...) -> InstalledFile
|
|
272
|
-
hasher = hashlib.sha256()
|
|
273
|
-
hashing.file_hash(path, digest=hasher)
|
|
274
|
-
return InstalledFile(
|
|
275
|
-
path=os.path.relpath(path, dest_dir),
|
|
276
|
-
hash=Hash.create(hasher),
|
|
277
|
-
size=os.stat(path).st_size,
|
|
278
|
-
)
|
|
279
|
-
|
|
280
|
-
def _create_record(
|
|
281
|
-
self,
|
|
282
|
-
dst, # type: Text
|
|
283
|
-
installed_files, # type: Iterable[InstalledFile]
|
|
284
|
-
):
|
|
285
|
-
# type: (...) -> None
|
|
286
|
-
Record.write(
|
|
287
|
-
dst=os.path.join(dst, self.record_relpath),
|
|
288
|
-
installed_files=[
|
|
289
|
-
# The RECORD entry should never include hash or size; so we replace any such entry
|
|
290
|
-
# with an un-hashed and un-sized one.
|
|
291
|
-
InstalledFile(self.record_relpath, hash=None, size=None)
|
|
292
|
-
if installed_file.path == self.record_relpath
|
|
293
|
-
else installed_file
|
|
294
|
-
for installed_file in installed_files
|
|
295
|
-
],
|
|
296
|
-
)
|
|
297
|
-
|
|
298
|
-
def reinstall_flat(
|
|
299
|
-
self,
|
|
300
|
-
target_dir, # type: str
|
|
301
|
-
copy_mode=CopyMode.LINK, # type: CopyMode.Value
|
|
302
|
-
):
|
|
303
|
-
# type: (...) -> Iterator[Tuple[Text, Text]]
|
|
304
|
-
"""Re-installs the installed wheel in a flat target directory.
|
|
305
|
-
|
|
306
|
-
N.B.: A record of reinstalled files is returned in the form of an iterator that must be
|
|
307
|
-
consumed to drive the installation to completion.
|
|
308
|
-
|
|
309
|
-
If there is an error re-installing a file due to it already existing in the target
|
|
310
|
-
directory, the error is suppressed, and it's expected that the caller detects this by
|
|
311
|
-
comparing the record of installed files against those installed previously.
|
|
312
|
-
|
|
313
|
-
:return: An iterator over src -> dst pairs.
|
|
314
|
-
"""
|
|
315
|
-
installed_files = [InstalledFile(self.record_relpath)]
|
|
316
|
-
for src, dst in itertools.chain(
|
|
317
|
-
self._reinstall_stash(dest_dir=target_dir, link=copy_mode is not CopyMode.COPY),
|
|
318
|
-
self._reinstall_site_packages(target_dir, copy_mode=copy_mode),
|
|
319
|
-
):
|
|
320
|
-
installed_files.append(self.create_installed_file(path=dst, dest_dir=target_dir))
|
|
321
|
-
yield src, dst
|
|
322
|
-
|
|
323
|
-
self._create_record(target_dir, installed_files)
|
|
324
|
-
|
|
325
|
-
def reinstall_venv(
|
|
326
|
-
self,
|
|
327
|
-
venv, # type: Virtualenv
|
|
328
|
-
copy_mode=CopyMode.LINK, # type: CopyMode.Value
|
|
329
|
-
rel_extra_path=None, # type: Optional[str]
|
|
330
|
-
):
|
|
331
|
-
# type: (...) -> Iterator[Tuple[Text, Text]]
|
|
332
|
-
"""Re-installs the installed wheel in a venv.
|
|
333
|
-
|
|
334
|
-
N.B.: A record of reinstalled files is returned in the form of an iterator that must be
|
|
335
|
-
consumed to drive the installation to completion.
|
|
336
|
-
|
|
337
|
-
If there is an error re-installing a file due to it already existing in the destination
|
|
338
|
-
venv, the error is suppressed, and it's expected that the caller detects this by comparing
|
|
339
|
-
the record of installed files against those installed previously.
|
|
340
|
-
|
|
341
|
-
:return: An iterator over src -> dst pairs.
|
|
342
|
-
"""
|
|
343
|
-
|
|
344
|
-
site_packages_dir = venv.purelib if self.root_is_purelib else venv.platlib
|
|
345
|
-
site_packages_dir = (
|
|
346
|
-
os.path.join(site_packages_dir, rel_extra_path) if rel_extra_path else site_packages_dir
|
|
347
|
-
)
|
|
348
|
-
|
|
349
|
-
installed_files = [InstalledFile(self.record_relpath)]
|
|
350
|
-
for src, dst in itertools.chain(
|
|
351
|
-
self._reinstall_stash(
|
|
352
|
-
dest_dir=venv.venv_dir,
|
|
353
|
-
interpreter=venv.interpreter,
|
|
354
|
-
link=copy_mode is not CopyMode.COPY,
|
|
355
|
-
),
|
|
356
|
-
self._reinstall_site_packages(site_packages_dir, copy_mode=copy_mode),
|
|
357
|
-
):
|
|
358
|
-
installed_files.append(self.create_installed_file(path=dst, dest_dir=site_packages_dir))
|
|
359
|
-
yield src, dst
|
|
360
|
-
|
|
361
|
-
self._create_record(site_packages_dir, installed_files)
|
|
362
|
-
|
|
363
|
-
def _reinstall_stash(
|
|
364
|
-
self,
|
|
365
|
-
dest_dir, # type: str
|
|
366
|
-
interpreter=None, # type: Optional[PythonInterpreter]
|
|
367
|
-
link=True, # type: bool
|
|
368
|
-
):
|
|
369
|
-
# type: (...) -> Iterator[Tuple[Text, Text]]
|
|
370
|
-
|
|
371
|
-
stash_abs_path = os.path.join(self.prefix_dir, self.stash_dir)
|
|
372
|
-
for root, dirs, files in os.walk(stash_abs_path, topdown=True, followlinks=True):
|
|
373
|
-
dir_created = False
|
|
374
|
-
for f in files:
|
|
375
|
-
src = os.path.join(root, f)
|
|
376
|
-
src_relpath = os.path.relpath(src, stash_abs_path)
|
|
377
|
-
dst = InstalledFile.denormalized_path(
|
|
378
|
-
path=os.path.join(dest_dir, src_relpath), interpreter=interpreter
|
|
379
|
-
)
|
|
380
|
-
if not dir_created:
|
|
381
|
-
safe_mkdir(os.path.dirname(dst))
|
|
382
|
-
dir_created = True
|
|
383
|
-
try:
|
|
384
|
-
# We only try to link regular files since linking a symlink on Linux can produce
|
|
385
|
-
# another symlink, which leaves open the possibility the src target could later
|
|
386
|
-
# go missing leaving the dst dangling.
|
|
387
|
-
if link and not os.path.islink(src):
|
|
388
|
-
try:
|
|
389
|
-
safe_link(src, dst)
|
|
390
|
-
continue
|
|
391
|
-
except OSError as e:
|
|
392
|
-
if e.errno != errno.EXDEV:
|
|
393
|
-
raise e
|
|
394
|
-
link = False
|
|
395
|
-
shutil.copy(src, dst)
|
|
396
|
-
except (IOError, OSError) as e:
|
|
397
|
-
if e.errno != errno.EEXIST:
|
|
398
|
-
raise e
|
|
399
|
-
finally:
|
|
400
|
-
yield src, dst
|
|
401
|
-
|
|
402
|
-
def _reinstall_site_packages(
|
|
403
|
-
self,
|
|
404
|
-
site_packages_dir, # type: str
|
|
405
|
-
copy_mode=CopyMode.LINK, # type: CopyMode.Value
|
|
406
|
-
):
|
|
407
|
-
# type: (...) -> Iterator[Tuple[Text, Text]]
|
|
408
|
-
|
|
409
|
-
link = copy_mode is CopyMode.LINK
|
|
410
|
-
for root, dirs, files in os.walk(self.prefix_dir, topdown=True, followlinks=True):
|
|
411
|
-
if root == self.prefix_dir:
|
|
412
|
-
dirs[:] = [d for d in dirs if not is_pyc_dir(d) and d != self.stash_dir]
|
|
413
|
-
files[:] = [
|
|
414
|
-
f for f in files if not is_pyc_file(f) and f != self._LAYOUT_JSON_FILENAME
|
|
415
|
-
]
|
|
416
|
-
|
|
417
|
-
traverse = set(dirs)
|
|
418
|
-
for path, is_dir in itertools.chain(
|
|
419
|
-
zip(dirs, itertools.repeat(True)), zip(files, itertools.repeat(False))
|
|
420
|
-
):
|
|
421
|
-
src_entry = os.path.join(root, path)
|
|
422
|
-
dst_entry = os.path.join(
|
|
423
|
-
site_packages_dir, os.path.relpath(src_entry, self.prefix_dir)
|
|
424
|
-
)
|
|
425
|
-
try:
|
|
426
|
-
if copy_mode is CopyMode.SYMLINK and not (
|
|
427
|
-
src_entry.endswith(".dist-info") and os.path.isdir(src_entry)
|
|
428
|
-
):
|
|
429
|
-
safe_relative_symlink(src_entry, dst_entry)
|
|
430
|
-
traverse.discard(path)
|
|
431
|
-
elif is_dir:
|
|
432
|
-
safe_mkdir(dst_entry)
|
|
433
|
-
else:
|
|
434
|
-
# We only try to link regular files since linking a symlink on Linux can
|
|
435
|
-
# produce another symlink, which leaves open the possibility the src_entry
|
|
436
|
-
# target could later go missing leaving the dst_entry dangling.
|
|
437
|
-
if link and not os.path.islink(src_entry):
|
|
438
|
-
try:
|
|
439
|
-
safe_link(src_entry, dst_entry)
|
|
440
|
-
continue
|
|
441
|
-
except OSError as e:
|
|
442
|
-
if e.errno != errno.EXDEV:
|
|
443
|
-
raise e
|
|
444
|
-
link = False
|
|
445
|
-
shutil.copy(src_entry, dst_entry)
|
|
446
|
-
except (IOError, OSError) as e:
|
|
447
|
-
if e.errno != errno.EEXIST:
|
|
448
|
-
raise e
|
|
449
|
-
finally:
|
|
450
|
-
if not is_dir:
|
|
451
|
-
yield src_entry, dst_entry
|
|
452
|
-
|
|
453
|
-
dirs[:] = list(traverse)
|
|
80
|
+
def create_installed_file(
|
|
81
|
+
path, # type: Text
|
|
82
|
+
dest_dir, # type: str
|
|
83
|
+
):
|
|
84
|
+
# type: (...) -> InstalledFile
|
|
85
|
+
hasher = hashlib.sha256()
|
|
86
|
+
hashing.file_hash(path, digest=hasher)
|
|
87
|
+
return InstalledFile(
|
|
88
|
+
path=os.path.relpath(path, dest_dir),
|
|
89
|
+
hash=Hash.create(hasher),
|
|
90
|
+
size=os.stat(path).st_size,
|
|
91
|
+
)
|
|
454
92
|
|
|
455
93
|
|
|
456
94
|
class RecordError(Exception):
|
|
@@ -483,10 +121,11 @@ class Record(object):
|
|
|
483
121
|
cls,
|
|
484
122
|
fp, # type: IO
|
|
485
123
|
installed_files, # type: Iterable[InstalledFile]
|
|
124
|
+
eol="\n", # type: str
|
|
486
125
|
):
|
|
487
126
|
# type: (...) -> None
|
|
488
|
-
csv_writer = csv.writer(fp, delimiter=",", quotechar='"', lineterminator=
|
|
489
|
-
for installed_file in
|
|
127
|
+
csv_writer = csv.writer(fp, delimiter=",", quotechar='"', lineterminator=eol)
|
|
128
|
+
for installed_file in installed_files:
|
|
490
129
|
csv_writer.writerow(attr.astuple(installed_file, recurse=False))
|
|
491
130
|
|
|
492
131
|
@classmethod
|
|
@@ -494,13 +133,14 @@ class Record(object):
|
|
|
494
133
|
cls,
|
|
495
134
|
dst, # type: Text
|
|
496
135
|
installed_files, # type: Iterable[InstalledFile]
|
|
136
|
+
eol="\n", # type: str
|
|
497
137
|
):
|
|
498
138
|
# type: (...) -> None
|
|
499
139
|
|
|
500
140
|
# The RECORD is a csv file with the path to each installed file in the 1st column.
|
|
501
141
|
# See: https://peps.python.org/pep-0376/#record
|
|
502
142
|
with safe_open(dst, "wb" if PY2 else "w") as fp:
|
|
503
|
-
cls.write_fp(fp, installed_files)
|
|
143
|
+
cls.write_fp(fp, installed_files, eol=eol)
|
|
504
144
|
|
|
505
145
|
@classmethod
|
|
506
146
|
def read(
|