pex 2.54.2__py2.py3-none-any.whl → 2.69.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/auth.py +1 -1
- pex/bin/pex.py +15 -2
- pex/build_backend/configuration.py +5 -5
- pex/build_backend/wrap.py +27 -23
- pex/build_system/pep_517.py +4 -1
- pex/cache/dirs.py +17 -12
- pex/cli/commands/lock.py +302 -165
- pex/cli/commands/pip/core.py +4 -12
- pex/cli/commands/pip/wheel.py +1 -1
- pex/cli/commands/run.py +13 -20
- pex/cli/commands/venv.py +85 -16
- pex/cli/pex.py +11 -4
- pex/common.py +57 -7
- pex/compatibility.py +1 -1
- pex/dependency_configuration.py +87 -15
- pex/dist_metadata.py +143 -25
- pex/docs/html/_pagefind/fragment/en_4250138.pf_fragment +0 -0
- pex/docs/html/_pagefind/fragment/en_7125dad.pf_fragment +0 -0
- pex/docs/html/_pagefind/fragment/en_785d562.pf_fragment +0 -0
- pex/docs/html/_pagefind/fragment/en_8e94bb8.pf_fragment +0 -0
- pex/docs/html/_pagefind/fragment/en_a0396bb.pf_fragment +0 -0
- pex/docs/html/_pagefind/fragment/en_a8a3588.pf_fragment +0 -0
- pex/docs/html/_pagefind/fragment/en_c07d988.pf_fragment +0 -0
- pex/docs/html/_pagefind/fragment/en_d718411.pf_fragment +0 -0
- pex/docs/html/_pagefind/index/en_a2e3c5e.pf_index +0 -0
- pex/docs/html/_pagefind/pagefind-entry.json +1 -1
- pex/docs/html/_pagefind/pagefind.en_4ce1afa9e3.pf_meta +0 -0
- pex/docs/html/_static/documentation_options.js +1 -1
- pex/docs/html/_static/pygments.css +164 -146
- pex/docs/html/_static/styles/furo.css +1 -1
- pex/docs/html/_static/styles/furo.css.map +1 -1
- pex/docs/html/api/vars.html +25 -34
- pex/docs/html/buildingpex.html +25 -34
- pex/docs/html/genindex.html +24 -33
- pex/docs/html/index.html +25 -34
- pex/docs/html/recipes.html +25 -34
- pex/docs/html/scie.html +25 -34
- pex/docs/html/search.html +24 -33
- pex/docs/html/whatispex.html +25 -34
- pex/entry_points_txt.py +98 -0
- pex/environment.py +54 -33
- pex/finders.py +1 -1
- pex/hashing.py +71 -9
- pex/installed_wheel.py +141 -0
- pex/interpreter.py +41 -38
- pex/interpreter_constraints.py +25 -25
- pex/interpreter_implementation.py +40 -0
- pex/jobs.py +13 -6
- pex/pep_376.py +68 -384
- pex/pep_425.py +11 -2
- pex/pep_427.py +937 -205
- pex/pep_508.py +4 -5
- pex/pex_builder.py +5 -8
- pex/pex_info.py +14 -9
- pex/pip/dependencies/__init__.py +85 -13
- pex/pip/dependencies/requires.py +38 -3
- pex/pip/foreign_platform/__init__.py +4 -3
- pex/pip/installation.py +2 -2
- pex/pip/local_project.py +6 -14
- pex/pip/package_repositories/__init__.py +78 -0
- pex/pip/package_repositories/link_collector.py +96 -0
- pex/pip/tool.py +139 -33
- pex/pip/vcs.py +109 -43
- pex/pip/version.py +8 -1
- pex/requirements.py +121 -16
- pex/resolve/config.py +5 -1
- pex/resolve/configured_resolve.py +32 -10
- pex/resolve/configured_resolver.py +10 -39
- pex/resolve/downloads.py +4 -3
- pex/resolve/lock_downloader.py +16 -23
- pex/resolve/lock_resolver.py +41 -51
- pex/resolve/locked_resolve.py +89 -32
- pex/resolve/locker.py +145 -101
- pex/resolve/locker_patches.py +123 -197
- pex/resolve/lockfile/create.py +232 -87
- pex/resolve/lockfile/download_manager.py +5 -1
- pex/resolve/lockfile/json_codec.py +103 -28
- pex/resolve/lockfile/model.py +13 -35
- pex/resolve/lockfile/pep_751.py +117 -98
- pex/resolve/lockfile/requires_dist.py +17 -262
- pex/resolve/lockfile/subset.py +11 -0
- pex/resolve/lockfile/targets.py +445 -0
- pex/resolve/lockfile/updater.py +22 -10
- pex/resolve/package_repository.py +406 -0
- pex/resolve/pex_repository_resolver.py +1 -1
- pex/resolve/pre_resolved_resolver.py +19 -16
- pex/resolve/project.py +233 -47
- pex/resolve/requirement_configuration.py +28 -10
- pex/resolve/resolver_configuration.py +18 -32
- pex/resolve/resolver_options.py +234 -28
- pex/resolve/resolvers.py +3 -12
- pex/resolve/target_options.py +18 -2
- pex/resolve/target_system.py +908 -0
- pex/resolve/venv_resolver.py +670 -0
- pex/resolver.py +673 -209
- pex/scie/__init__.py +40 -1
- pex/scie/model.py +2 -0
- pex/scie/science.py +25 -3
- pex/sdist.py +219 -0
- pex/sh_boot.py +24 -21
- pex/sysconfig.py +5 -3
- pex/targets.py +31 -10
- pex/third_party/__init__.py +1 -1
- pex/tools/commands/repository.py +48 -25
- pex/vendor/__init__.py +4 -9
- pex/vendor/__main__.py +65 -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/_vendor/certifi/cacert.pem +63 -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 +46 -19
- pex/venv/venv_pex.py +6 -3
- pex/version.py +1 -1
- pex/wheel.py +188 -40
- pex/whl.py +67 -0
- pex/windows/__init__.py +14 -11
- {pex-2.54.2.dist-info → pex-2.69.0.dist-info}/METADATA +6 -5
- {pex-2.54.2.dist-info → pex-2.69.0.dist-info}/RECORD +157 -133
- {pex-2.54.2.dist-info → pex-2.69.0.dist-info}/entry_points.txt +1 -0
- {pex-2.54.2.dist-info → pex-2.69.0.dist-info}/pylock/pylock.toml +1 -1
- pex/docs/html/_pagefind/fragment/en_42c9d8c.pf_fragment +0 -0
- pex/docs/html/_pagefind/fragment/en_45dd5a2.pf_fragment +0 -0
- pex/docs/html/_pagefind/fragment/en_4ca74d2.pf_fragment +0 -0
- pex/docs/html/_pagefind/fragment/en_77273d5.pf_fragment +0 -0
- pex/docs/html/_pagefind/fragment/en_87a59c5.pf_fragment +0 -0
- pex/docs/html/_pagefind/fragment/en_8dc89b5.pf_fragment +0 -0
- pex/docs/html/_pagefind/fragment/en_9d1319b.pf_fragment +0 -0
- pex/docs/html/_pagefind/fragment/en_e55df9d.pf_fragment +0 -0
- pex/docs/html/_pagefind/index/en_1e98c6f.pf_index +0 -0
- pex/docs/html/_pagefind/pagefind.en_d1c488ecae.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.54.2.dist-info → pex-2.69.0.dist-info}/WHEEL +0 -0
- {pex-2.54.2.dist-info → pex-2.69.0.dist-info}/licenses/LICENSE +0 -0
- {pex-2.54.2.dist-info → pex-2.69.0.dist-info}/top_level.txt +0 -0
pex/pep_376.py
CHANGED
|
@@ -5,32 +5,18 @@ from __future__ import absolute_import
|
|
|
5
5
|
|
|
6
6
|
import base64
|
|
7
7
|
import csv
|
|
8
|
-
import errno
|
|
9
8
|
import hashlib
|
|
10
|
-
import
|
|
11
|
-
import json
|
|
9
|
+
import io
|
|
12
10
|
import os
|
|
13
|
-
import shutil
|
|
14
11
|
from fileinput import FileInput
|
|
15
12
|
|
|
16
13
|
from pex import hashing
|
|
17
|
-
from pex.common import
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
is_pyc_file,
|
|
21
|
-
safe_mkdir,
|
|
22
|
-
safe_open,
|
|
23
|
-
safe_relative_symlink,
|
|
24
|
-
)
|
|
25
|
-
from pex.fs import safe_link
|
|
26
|
-
from pex.interpreter import PythonInterpreter
|
|
27
|
-
from pex.typing import TYPE_CHECKING, cast
|
|
28
|
-
from pex.util import CacheHelper
|
|
29
|
-
from pex.venv.virtualenv import Virtualenv
|
|
30
|
-
from pex.wheel import WHEEL, Wheel, WheelMetadataLoadError
|
|
14
|
+
from pex.common import safe_open
|
|
15
|
+
from pex.compatibility import PY2
|
|
16
|
+
from pex.typing import TYPE_CHECKING
|
|
31
17
|
|
|
32
18
|
if TYPE_CHECKING:
|
|
33
|
-
from typing import Callable, Iterable, Iterator, Optional, Protocol, Text,
|
|
19
|
+
from typing import IO, Callable, Iterable, Iterator, Optional, Protocol, Text, Union
|
|
34
20
|
|
|
35
21
|
import attr # vendor:skip
|
|
36
22
|
|
|
@@ -66,7 +52,12 @@ class Hash(object):
|
|
|
66
52
|
# + https://peps.python.org/pep-0376/#record
|
|
67
53
|
# + https://peps.python.org/pep-0427/#appendix
|
|
68
54
|
fingerprint = base64.urlsafe_b64encode(hasher.digest()).rstrip(b"=")
|
|
69
|
-
|
|
55
|
+
|
|
56
|
+
# N.B.: The algorithm is all caps under Python 2.7, but lower case under Python 3; so we
|
|
57
|
+
# normalize.
|
|
58
|
+
alg = hasher.name.lower()
|
|
59
|
+
|
|
60
|
+
return cls(value="{alg}={hash}".format(alg=alg, hash=fingerprint.decode("ascii")))
|
|
70
61
|
|
|
71
62
|
value = attr.ib() # type: str
|
|
72
63
|
|
|
@@ -75,39 +66,6 @@ class Hash(object):
|
|
|
75
66
|
return self.value
|
|
76
67
|
|
|
77
68
|
|
|
78
|
-
def find_and_replace_path_components(
|
|
79
|
-
path, # type: Text
|
|
80
|
-
find, # type: str
|
|
81
|
-
replace, # type: str
|
|
82
|
-
):
|
|
83
|
-
# type: (...) -> Text
|
|
84
|
-
"""Replace components of `path` that are exactly `find` with `replace`.
|
|
85
|
-
|
|
86
|
-
>>> find_and_replace_path_components("foo/bar/baz", "bar", "spam")
|
|
87
|
-
foo/spam/baz
|
|
88
|
-
>>>
|
|
89
|
-
"""
|
|
90
|
-
if not find or not replace:
|
|
91
|
-
raise ValueError(
|
|
92
|
-
"Both find and replace must be non-empty strings. Given find={find!r} "
|
|
93
|
-
"replace={replace!r}".format(find=find, replace=replace)
|
|
94
|
-
)
|
|
95
|
-
if not path:
|
|
96
|
-
return path
|
|
97
|
-
|
|
98
|
-
components = []
|
|
99
|
-
head = path
|
|
100
|
-
while head:
|
|
101
|
-
new_head, tail = os.path.split(head)
|
|
102
|
-
if new_head == head:
|
|
103
|
-
components.append(head)
|
|
104
|
-
break
|
|
105
|
-
components.append(tail)
|
|
106
|
-
head = new_head
|
|
107
|
-
components.reverse()
|
|
108
|
-
return os.path.join(*(replace if component == find else component for component in components))
|
|
109
|
-
|
|
110
|
-
|
|
111
69
|
@attr.s(frozen=True)
|
|
112
70
|
class InstalledFile(object):
|
|
113
71
|
"""The record of a single installed file from a PEP 376 RECORD file.
|
|
@@ -115,336 +73,31 @@ class InstalledFile(object):
|
|
|
115
73
|
See: https://peps.python.org/pep-0376/#record
|
|
116
74
|
"""
|
|
117
75
|
|
|
118
|
-
_PYTHON_VER_PLACEHOLDER = "pythonX.Y"
|
|
119
|
-
|
|
120
|
-
@staticmethod
|
|
121
|
-
def _python_ver(interpreter=None):
|
|
122
|
-
# type: (Optional[PythonInterpreter]) -> str
|
|
123
|
-
python = interpreter or PythonInterpreter.get()
|
|
124
|
-
return "python{major}.{minor}".format(major=python.version[0], minor=python.version[1])
|
|
125
|
-
|
|
126
|
-
@classmethod
|
|
127
|
-
def normalized_path(
|
|
128
|
-
cls,
|
|
129
|
-
path, # type: Text
|
|
130
|
-
interpreter=None, # type: Optional[PythonInterpreter]
|
|
131
|
-
):
|
|
132
|
-
# type: (...) -> Text
|
|
133
|
-
return find_and_replace_path_components(
|
|
134
|
-
path, cls._python_ver(interpreter=interpreter), cls._PYTHON_VER_PLACEHOLDER
|
|
135
|
-
)
|
|
136
|
-
|
|
137
|
-
@classmethod
|
|
138
|
-
def denormalized_path(
|
|
139
|
-
cls,
|
|
140
|
-
path, # type: str
|
|
141
|
-
interpreter=None, # type: Optional[PythonInterpreter]
|
|
142
|
-
):
|
|
143
|
-
# type: (...) -> Text
|
|
144
|
-
return find_and_replace_path_components(
|
|
145
|
-
path, cls._PYTHON_VER_PLACEHOLDER, cls._python_ver(interpreter=interpreter)
|
|
146
|
-
)
|
|
147
|
-
|
|
148
76
|
path = attr.ib() # type: Text
|
|
149
77
|
hash = attr.ib(default=None) # type: Optional[Hash]
|
|
150
78
|
size = attr.ib(default=None) # type: Optional[int]
|
|
151
79
|
|
|
152
80
|
|
|
153
81
|
@attr.s(frozen=True)
|
|
154
|
-
class
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
_LAYOUT_JSON_FILENAME = ".layout.json"
|
|
159
|
-
|
|
160
|
-
@classmethod
|
|
161
|
-
def layout_file(cls, prefix_dir):
|
|
162
|
-
# type: (str) -> str
|
|
163
|
-
return os.path.join(prefix_dir, cls._LAYOUT_JSON_FILENAME)
|
|
164
|
-
|
|
165
|
-
@classmethod
|
|
166
|
-
def save(
|
|
167
|
-
cls,
|
|
168
|
-
prefix_dir, # type: str
|
|
169
|
-
stash_dir, # type: str
|
|
170
|
-
record_relpath, # type: Text
|
|
171
|
-
root_is_purelib, # type: bool
|
|
172
|
-
):
|
|
173
|
-
# type: (...) -> InstalledWheel
|
|
174
|
-
|
|
175
|
-
# We currently need the installed wheel chroot hash for PEX-INFO / boot purposes. It is
|
|
176
|
-
# expensive to calculate; so we do it here 1 time when saving the installed wheel.
|
|
177
|
-
fingerprint = CacheHelper.dir_hash(prefix_dir, hasher=hashlib.sha256)
|
|
178
|
-
|
|
179
|
-
layout = {
|
|
180
|
-
"stash_dir": stash_dir,
|
|
181
|
-
"record_relpath": record_relpath,
|
|
182
|
-
"fingerprint": fingerprint,
|
|
183
|
-
"root_is_purelib": root_is_purelib,
|
|
184
|
-
}
|
|
185
|
-
with open(cls.layout_file(prefix_dir), "w") as fp:
|
|
186
|
-
json.dump(layout, fp, sort_keys=True)
|
|
187
|
-
return cls(
|
|
188
|
-
prefix_dir=prefix_dir,
|
|
189
|
-
stash_dir=stash_dir,
|
|
190
|
-
record_relpath=record_relpath,
|
|
191
|
-
fingerprint=fingerprint,
|
|
192
|
-
root_is_purelib=root_is_purelib,
|
|
193
|
-
)
|
|
82
|
+
class InstalledDirectory(object):
|
|
83
|
+
# N.B.: Although directory entries should not exist in a RECORD, they have been seen in the
|
|
84
|
+
# wild; so we're forced to deal with them. See: https://github.com/pex-tool/pex/issues/2998
|
|
194
85
|
|
|
195
|
-
|
|
196
|
-
def load(cls, prefix_dir):
|
|
197
|
-
# type: (str) -> InstalledWheel
|
|
198
|
-
layout_file = cls.layout_file(prefix_dir)
|
|
199
|
-
try:
|
|
200
|
-
with open(layout_file) as fp:
|
|
201
|
-
layout = json.load(fp)
|
|
202
|
-
except (IOError, OSError) as e:
|
|
203
|
-
raise cls.LoadError(
|
|
204
|
-
"Failed to load an installed wheel layout from {layout_file}: {err}".format(
|
|
205
|
-
layout_file=layout_file, err=e
|
|
206
|
-
)
|
|
207
|
-
)
|
|
208
|
-
if not isinstance(layout, dict):
|
|
209
|
-
raise cls.LoadError(
|
|
210
|
-
"The installed wheel layout file at {layout_file} must contain a single top-level "
|
|
211
|
-
"object, found: {value}.".format(layout_file=layout_file, value=layout)
|
|
212
|
-
)
|
|
213
|
-
stash_dir = layout.get("stash_dir")
|
|
214
|
-
record_relpath = layout.get("record_relpath")
|
|
215
|
-
if not stash_dir or not record_relpath:
|
|
216
|
-
raise cls.LoadError(
|
|
217
|
-
"The installed wheel layout file at {layout_file} must contain an object with both "
|
|
218
|
-
"`stash_dir` and `record_relpath` attributes, found: {value}".format(
|
|
219
|
-
layout_file=layout_file, value=layout
|
|
220
|
-
)
|
|
221
|
-
)
|
|
222
|
-
|
|
223
|
-
fingerprint = layout.get("fingerprint")
|
|
224
|
-
|
|
225
|
-
# N.B.: Caching root_is_purelib was not part of the original InstalledWheel layout data; so
|
|
226
|
-
# we materialize the property if needed to support older installed wheel chroots.
|
|
227
|
-
root_is_purelib = layout.get("root_is_purelib")
|
|
228
|
-
if root_is_purelib is None:
|
|
229
|
-
try:
|
|
230
|
-
wheel = WHEEL.load(prefix_dir)
|
|
231
|
-
except WheelMetadataLoadError as e:
|
|
232
|
-
raise cls.LoadError(
|
|
233
|
-
"Failed to determine if installed wheel at {location} is platform-specific: "
|
|
234
|
-
"{err}".format(location=prefix_dir, err=e)
|
|
235
|
-
)
|
|
236
|
-
root_is_purelib = wheel.root_is_purelib
|
|
237
|
-
|
|
238
|
-
return cls(
|
|
239
|
-
prefix_dir=prefix_dir,
|
|
240
|
-
stash_dir=cast(str, stash_dir),
|
|
241
|
-
record_relpath=cast(str, record_relpath),
|
|
242
|
-
fingerprint=cast("Optional[str]", fingerprint),
|
|
243
|
-
root_is_purelib=root_is_purelib,
|
|
244
|
-
)
|
|
86
|
+
dir_info = attr.ib() # type: InstalledFile
|
|
245
87
|
|
|
246
|
-
prefix_dir = attr.ib() # type: str
|
|
247
|
-
stash_dir = attr.ib() # type: str
|
|
248
|
-
record_relpath = attr.ib() # type: Text
|
|
249
|
-
fingerprint = attr.ib() # type: Optional[str]
|
|
250
|
-
root_is_purelib = attr.ib() # type: bool
|
|
251
|
-
|
|
252
|
-
def wheel_file_name(self):
|
|
253
|
-
# type: () -> str
|
|
254
|
-
return Wheel.load(self.prefix_dir).wheel_file_name
|
|
255
88
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
return InstalledFile(
|
|
269
|
-
path=os.path.relpath(path, dest_dir),
|
|
270
|
-
hash=Hash.create(hasher),
|
|
271
|
-
size=os.stat(path).st_size,
|
|
272
|
-
)
|
|
273
|
-
|
|
274
|
-
def _create_record(
|
|
275
|
-
self,
|
|
276
|
-
dst, # type: Text
|
|
277
|
-
installed_files, # type: Iterable[InstalledFile]
|
|
278
|
-
):
|
|
279
|
-
# type: (...) -> None
|
|
280
|
-
Record.write(
|
|
281
|
-
dst=os.path.join(dst, self.record_relpath),
|
|
282
|
-
installed_files=[
|
|
283
|
-
# The RECORD entry should never include hash or size; so we replace any such entry
|
|
284
|
-
# with an un-hashed and un-sized one.
|
|
285
|
-
InstalledFile(self.record_relpath, hash=None, size=None)
|
|
286
|
-
if installed_file.path == self.record_relpath
|
|
287
|
-
else installed_file
|
|
288
|
-
for installed_file in installed_files
|
|
289
|
-
],
|
|
290
|
-
)
|
|
291
|
-
|
|
292
|
-
def reinstall_flat(
|
|
293
|
-
self,
|
|
294
|
-
target_dir, # type: str
|
|
295
|
-
copy_mode=CopyMode.LINK, # type: CopyMode.Value
|
|
296
|
-
):
|
|
297
|
-
# type: (...) -> Iterator[Tuple[Text, Text]]
|
|
298
|
-
"""Re-installs the installed wheel in a flat target directory.
|
|
299
|
-
|
|
300
|
-
N.B.: A record of reinstalled files is returned in the form of an iterator that must be
|
|
301
|
-
consumed to drive the installation to completion.
|
|
302
|
-
|
|
303
|
-
If there is an error re-installing a file due to it already existing in the target
|
|
304
|
-
directory, the error is suppressed, and it's expected that the caller detects this by
|
|
305
|
-
comparing the record of installed files against those installed previously.
|
|
306
|
-
|
|
307
|
-
:return: An iterator over src -> dst pairs.
|
|
308
|
-
"""
|
|
309
|
-
installed_files = [InstalledFile(self.record_relpath)]
|
|
310
|
-
for src, dst in itertools.chain(
|
|
311
|
-
self._reinstall_stash(dest_dir=target_dir, link=copy_mode is not CopyMode.COPY),
|
|
312
|
-
self._reinstall_site_packages(target_dir, copy_mode=copy_mode),
|
|
313
|
-
):
|
|
314
|
-
installed_files.append(self.create_installed_file(path=dst, dest_dir=target_dir))
|
|
315
|
-
yield src, dst
|
|
316
|
-
|
|
317
|
-
self._create_record(target_dir, installed_files)
|
|
318
|
-
|
|
319
|
-
def reinstall_venv(
|
|
320
|
-
self,
|
|
321
|
-
venv, # type: Virtualenv
|
|
322
|
-
copy_mode=CopyMode.LINK, # type: CopyMode.Value
|
|
323
|
-
rel_extra_path=None, # type: Optional[str]
|
|
324
|
-
):
|
|
325
|
-
# type: (...) -> Iterator[Tuple[Text, Text]]
|
|
326
|
-
"""Re-installs the installed wheel in a venv.
|
|
327
|
-
|
|
328
|
-
N.B.: A record of reinstalled files is returned in the form of an iterator that must be
|
|
329
|
-
consumed to drive the installation to completion.
|
|
330
|
-
|
|
331
|
-
If there is an error re-installing a file due to it already existing in the destination
|
|
332
|
-
venv, the error is suppressed, and it's expected that the caller detects this by comparing
|
|
333
|
-
the record of installed files against those installed previously.
|
|
334
|
-
|
|
335
|
-
:return: An iterator over src -> dst pairs.
|
|
336
|
-
"""
|
|
337
|
-
|
|
338
|
-
site_packages_dir = venv.purelib if self.root_is_purelib else venv.platlib
|
|
339
|
-
site_packages_dir = (
|
|
340
|
-
os.path.join(site_packages_dir, rel_extra_path) if rel_extra_path else site_packages_dir
|
|
341
|
-
)
|
|
342
|
-
|
|
343
|
-
installed_files = [InstalledFile(self.record_relpath)]
|
|
344
|
-
for src, dst in itertools.chain(
|
|
345
|
-
self._reinstall_stash(
|
|
346
|
-
dest_dir=venv.venv_dir,
|
|
347
|
-
interpreter=venv.interpreter,
|
|
348
|
-
link=copy_mode is not CopyMode.COPY,
|
|
349
|
-
),
|
|
350
|
-
self._reinstall_site_packages(site_packages_dir, copy_mode=copy_mode),
|
|
351
|
-
):
|
|
352
|
-
installed_files.append(self.create_installed_file(path=dst, dest_dir=site_packages_dir))
|
|
353
|
-
yield src, dst
|
|
354
|
-
|
|
355
|
-
self._create_record(site_packages_dir, installed_files)
|
|
356
|
-
|
|
357
|
-
def _reinstall_stash(
|
|
358
|
-
self,
|
|
359
|
-
dest_dir, # type: str
|
|
360
|
-
interpreter=None, # type: Optional[PythonInterpreter]
|
|
361
|
-
link=True, # type: bool
|
|
362
|
-
):
|
|
363
|
-
# type: (...) -> Iterator[Tuple[Text, Text]]
|
|
364
|
-
|
|
365
|
-
stash_abs_path = os.path.join(self.prefix_dir, self.stash_dir)
|
|
366
|
-
for root, dirs, files in os.walk(stash_abs_path, topdown=True, followlinks=True):
|
|
367
|
-
dir_created = False
|
|
368
|
-
for f in files:
|
|
369
|
-
src = os.path.join(root, f)
|
|
370
|
-
src_relpath = os.path.relpath(src, stash_abs_path)
|
|
371
|
-
dst = InstalledFile.denormalized_path(
|
|
372
|
-
path=os.path.join(dest_dir, src_relpath), interpreter=interpreter
|
|
373
|
-
)
|
|
374
|
-
if not dir_created:
|
|
375
|
-
safe_mkdir(os.path.dirname(dst))
|
|
376
|
-
dir_created = True
|
|
377
|
-
try:
|
|
378
|
-
# We only try to link regular files since linking a symlink on Linux can produce
|
|
379
|
-
# another symlink, which leaves open the possibility the src target could later
|
|
380
|
-
# go missing leaving the dst dangling.
|
|
381
|
-
if link and not os.path.islink(src):
|
|
382
|
-
try:
|
|
383
|
-
safe_link(src, dst)
|
|
384
|
-
continue
|
|
385
|
-
except OSError as e:
|
|
386
|
-
if e.errno != errno.EXDEV:
|
|
387
|
-
raise e
|
|
388
|
-
link = False
|
|
389
|
-
shutil.copy(src, dst)
|
|
390
|
-
except (IOError, OSError) as e:
|
|
391
|
-
if e.errno != errno.EEXIST:
|
|
392
|
-
raise e
|
|
393
|
-
finally:
|
|
394
|
-
yield src, dst
|
|
395
|
-
|
|
396
|
-
def _reinstall_site_packages(
|
|
397
|
-
self,
|
|
398
|
-
site_packages_dir, # type: str
|
|
399
|
-
copy_mode=CopyMode.LINK, # type: CopyMode.Value
|
|
400
|
-
):
|
|
401
|
-
# type: (...) -> Iterator[Tuple[Text, Text]]
|
|
402
|
-
|
|
403
|
-
link = copy_mode is CopyMode.LINK
|
|
404
|
-
for root, dirs, files in os.walk(self.prefix_dir, topdown=True, followlinks=True):
|
|
405
|
-
if root == self.prefix_dir:
|
|
406
|
-
dirs[:] = [d for d in dirs if not is_pyc_dir(d) and d != self.stash_dir]
|
|
407
|
-
files[:] = [
|
|
408
|
-
f for f in files if not is_pyc_file(f) and f != self._LAYOUT_JSON_FILENAME
|
|
409
|
-
]
|
|
410
|
-
|
|
411
|
-
traverse = set(dirs)
|
|
412
|
-
for path, is_dir in itertools.chain(
|
|
413
|
-
zip(dirs, itertools.repeat(True)), zip(files, itertools.repeat(False))
|
|
414
|
-
):
|
|
415
|
-
src_entry = os.path.join(root, path)
|
|
416
|
-
dst_entry = os.path.join(
|
|
417
|
-
site_packages_dir, os.path.relpath(src_entry, self.prefix_dir)
|
|
418
|
-
)
|
|
419
|
-
try:
|
|
420
|
-
if copy_mode is CopyMode.SYMLINK and not (
|
|
421
|
-
src_entry.endswith(".dist-info") and os.path.isdir(src_entry)
|
|
422
|
-
):
|
|
423
|
-
safe_relative_symlink(src_entry, dst_entry)
|
|
424
|
-
traverse.discard(path)
|
|
425
|
-
elif is_dir:
|
|
426
|
-
safe_mkdir(dst_entry)
|
|
427
|
-
else:
|
|
428
|
-
# We only try to link regular files since linking a symlink on Linux can
|
|
429
|
-
# produce another symlink, which leaves open the possibility the src_entry
|
|
430
|
-
# target could later go missing leaving the dst_entry dangling.
|
|
431
|
-
if link and not os.path.islink(src_entry):
|
|
432
|
-
try:
|
|
433
|
-
safe_link(src_entry, dst_entry)
|
|
434
|
-
continue
|
|
435
|
-
except OSError as e:
|
|
436
|
-
if e.errno != errno.EXDEV:
|
|
437
|
-
raise e
|
|
438
|
-
link = False
|
|
439
|
-
shutil.copy(src_entry, dst_entry)
|
|
440
|
-
except (IOError, OSError) as e:
|
|
441
|
-
if e.errno != errno.EEXIST:
|
|
442
|
-
raise e
|
|
443
|
-
finally:
|
|
444
|
-
if not is_dir:
|
|
445
|
-
yield src_entry, dst_entry
|
|
446
|
-
|
|
447
|
-
dirs[:] = list(traverse)
|
|
89
|
+
def create_installed_file(
|
|
90
|
+
path, # type: Text
|
|
91
|
+
dest_dir, # type: str
|
|
92
|
+
):
|
|
93
|
+
# type: (...) -> InstalledFile
|
|
94
|
+
hasher = hashlib.sha256()
|
|
95
|
+
hashing.file_hash(path, digest=hasher)
|
|
96
|
+
return InstalledFile(
|
|
97
|
+
path=os.path.relpath(path, dest_dir),
|
|
98
|
+
hash=Hash.create(hasher),
|
|
99
|
+
size=os.stat(path).st_size,
|
|
100
|
+
)
|
|
448
101
|
|
|
449
102
|
|
|
450
103
|
class RecordError(Exception):
|
|
@@ -472,23 +125,50 @@ class Record(object):
|
|
|
472
125
|
See: https://peps.python.org/pep-0376/#record
|
|
473
126
|
"""
|
|
474
127
|
|
|
128
|
+
@classmethod
|
|
129
|
+
def write_fp(
|
|
130
|
+
cls,
|
|
131
|
+
fp, # type: IO
|
|
132
|
+
installed_files, # type: Iterable[Union[InstalledFile, InstalledDirectory]]
|
|
133
|
+
eol="\n", # type: str
|
|
134
|
+
):
|
|
135
|
+
# type: (...) -> None
|
|
136
|
+
csv_writer = csv.writer(fp, delimiter=",", quotechar='"', lineterminator=eol)
|
|
137
|
+
for installed_file in installed_files:
|
|
138
|
+
if isinstance(installed_file, InstalledDirectory):
|
|
139
|
+
csv_writer.writerow(attr.astuple(installed_file.dir_info, recurse=False))
|
|
140
|
+
else:
|
|
141
|
+
csv_writer.writerow(attr.astuple(installed_file, recurse=False))
|
|
142
|
+
|
|
143
|
+
@classmethod
|
|
144
|
+
def write_bytes(
|
|
145
|
+
cls,
|
|
146
|
+
installed_files, # type: Iterable[Union[InstalledFile, InstalledDirectory]]
|
|
147
|
+
eol="\n", # type: str
|
|
148
|
+
):
|
|
149
|
+
# type: (...) -> bytes
|
|
150
|
+
if PY2:
|
|
151
|
+
record_fp = io.BytesIO()
|
|
152
|
+
cls.write_fp(fp=record_fp, installed_files=installed_files, eol=eol)
|
|
153
|
+
return record_fp.getvalue()
|
|
154
|
+
else:
|
|
155
|
+
record_fp = io.StringIO()
|
|
156
|
+
cls.write_fp(fp=record_fp, installed_files=installed_files, eol=eol)
|
|
157
|
+
return record_fp.getvalue().encode("utf-8")
|
|
158
|
+
|
|
475
159
|
@classmethod
|
|
476
160
|
def write(
|
|
477
161
|
cls,
|
|
478
162
|
dst, # type: Text
|
|
479
|
-
installed_files, # type: Iterable[InstalledFile]
|
|
163
|
+
installed_files, # type: Iterable[Union[InstalledFile, InstalledDirectory]]
|
|
164
|
+
eol="\n", # type: str
|
|
480
165
|
):
|
|
481
166
|
# type: (...) -> None
|
|
482
167
|
|
|
483
168
|
# The RECORD is a csv file with the path to each installed file in the 1st column.
|
|
484
169
|
# See: https://peps.python.org/pep-0376/#record
|
|
485
|
-
with safe_open(dst, "w") as fp:
|
|
486
|
-
|
|
487
|
-
"CSVWriter",
|
|
488
|
-
csv.writer(fp, delimiter=",", quotechar='"', lineterminator="\n"),
|
|
489
|
-
)
|
|
490
|
-
for installed_file in sorted(installed_files, key=lambda installed: installed.path):
|
|
491
|
-
csv_writer.writerow(attr.astuple(installed_file, recurse=False))
|
|
170
|
+
with safe_open(dst, "wb" if PY2 else "w") as fp:
|
|
171
|
+
cls.write_fp(fp, installed_files, eol=eol)
|
|
492
172
|
|
|
493
173
|
@classmethod
|
|
494
174
|
def read(
|
|
@@ -496,7 +176,7 @@ class Record(object):
|
|
|
496
176
|
lines, # type: Union[FileInput[Text], Iterator[Text]]
|
|
497
177
|
exclude=None, # type: Optional[Callable[[Text], bool]]
|
|
498
178
|
):
|
|
499
|
-
# type: (...) -> Iterator[InstalledFile]
|
|
179
|
+
# type: (...) -> Iterator[Union[InstalledFile, InstalledDirectory]]
|
|
500
180
|
|
|
501
181
|
# The RECORD is a csv file with the path to each installed file in the 1st column.
|
|
502
182
|
# See: https://peps.python.org/pep-0376/#record
|
|
@@ -508,7 +188,11 @@ class Record(object):
|
|
|
508
188
|
continue
|
|
509
189
|
file_hash = Hash(fingerprint) if fingerprint else None
|
|
510
190
|
size = int(file_size) if file_size else None
|
|
511
|
-
|
|
191
|
+
installed_file = InstalledFile(path=path, hash=file_hash, size=size)
|
|
192
|
+
if path.endswith("/"):
|
|
193
|
+
yield InstalledDirectory(dir_info=installed_file)
|
|
194
|
+
else:
|
|
195
|
+
yield installed_file
|
|
512
196
|
|
|
513
197
|
project_name = attr.ib() # type: str
|
|
514
198
|
version = attr.ib() # type: str
|
pex/pep_425.py
CHANGED
|
@@ -6,11 +6,12 @@ from __future__ import absolute_import
|
|
|
6
6
|
import itertools
|
|
7
7
|
import os.path
|
|
8
8
|
|
|
9
|
-
from pex.dist_metadata import is_wheel
|
|
9
|
+
from pex.dist_metadata import Distribution, is_wheel
|
|
10
10
|
from pex.orderedset import OrderedSet
|
|
11
11
|
from pex.rank import Rank
|
|
12
12
|
from pex.third_party.packaging.tags import Tag, parse_tag
|
|
13
13
|
from pex.typing import TYPE_CHECKING, cast, overload
|
|
14
|
+
from pex.wheel import WHEEL
|
|
14
15
|
|
|
15
16
|
if TYPE_CHECKING:
|
|
16
17
|
from typing import (
|
|
@@ -66,7 +67,13 @@ class CompatibilityTags(object):
|
|
|
66
67
|
|
|
67
68
|
@classmethod
|
|
68
69
|
def from_wheel(cls, wheel):
|
|
69
|
-
# type: (Text) -> CompatibilityTags
|
|
70
|
+
# type: (Union[Text, Distribution]) -> CompatibilityTags
|
|
71
|
+
|
|
72
|
+
if isinstance(wheel, Distribution):
|
|
73
|
+
if not is_wheel(wheel.location):
|
|
74
|
+
return cls(tags=WHEEL.from_distribution(wheel).tags)
|
|
75
|
+
wheel = wheel.location
|
|
76
|
+
|
|
70
77
|
if not is_wheel(wheel):
|
|
71
78
|
raise ValueError(
|
|
72
79
|
"Can only calculate wheel tags from a filename that ends in .whl per "
|
|
@@ -74,6 +81,7 @@ class CompatibilityTags(object):
|
|
|
74
81
|
wheel=wheel
|
|
75
82
|
)
|
|
76
83
|
)
|
|
84
|
+
|
|
77
85
|
wheel_stem, _ = os.path.splitext(os.path.basename(wheel))
|
|
78
86
|
# Wheel filename format: https://peps.python.org/pep-0427/#file-name-convention
|
|
79
87
|
# `{distribution}-{version}(-{build tag})?-{python tag}-{abi tag}-{platform tag}.whl`
|
|
@@ -86,6 +94,7 @@ class CompatibilityTags(object):
|
|
|
86
94
|
pattern=pattern, wheel=wheel
|
|
87
95
|
)
|
|
88
96
|
)
|
|
97
|
+
|
|
89
98
|
return cls(tags=tuple(parse_tag("-".join(wheel_components[-3:]))))
|
|
90
99
|
|
|
91
100
|
@classmethod
|