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/scie/__init__.py
CHANGED
|
@@ -222,6 +222,32 @@ def register_options(parser):
|
|
|
222
222
|
"to the patch level."
|
|
223
223
|
),
|
|
224
224
|
)
|
|
225
|
+
parser.add_argument(
|
|
226
|
+
"--scie-pbs-free-threaded",
|
|
227
|
+
"--no-scie-pbs-free-threaded",
|
|
228
|
+
dest="scie_pbs_free_threaded",
|
|
229
|
+
default=False,
|
|
230
|
+
type=bool,
|
|
231
|
+
action=HandleBoolAction,
|
|
232
|
+
help=(
|
|
233
|
+
"Should the Python Standalone Builds CPython distributions be free-threaded. If left "
|
|
234
|
+
"unspecified or otherwise turned off, creating a scie from a PEX with free-threaded "
|
|
235
|
+
"abi wheels will automatically turn this option on. Note that this option is not "
|
|
236
|
+
"compatible with `--scie-pbs-stripped`."
|
|
237
|
+
),
|
|
238
|
+
)
|
|
239
|
+
parser.add_argument(
|
|
240
|
+
"--scie-pbs-debug",
|
|
241
|
+
"--no-scie-pbs-debug",
|
|
242
|
+
dest="scie_pbs_debug",
|
|
243
|
+
default=False,
|
|
244
|
+
type=bool,
|
|
245
|
+
action=HandleBoolAction,
|
|
246
|
+
help=(
|
|
247
|
+
"Should the Python Standalone Builds CPython distributions be debug builds. Note that "
|
|
248
|
+
"this option is not compatible with `--scie-pbs-stripped`."
|
|
249
|
+
),
|
|
250
|
+
)
|
|
225
251
|
parser.add_argument(
|
|
226
252
|
"--scie-pbs-stripped",
|
|
227
253
|
"--no-scie-pbs-stripped",
|
|
@@ -232,7 +258,8 @@ def register_options(parser):
|
|
|
232
258
|
help=(
|
|
233
259
|
"Should the Python Standalone Builds CPython distributions used be stripped of debug "
|
|
234
260
|
"symbols or not. For Linux and Windows particularly, the stripped distributions are "
|
|
235
|
-
"less than half the size of the distributions that ship with debug symbols."
|
|
261
|
+
"less than half the size of the distributions that ship with debug symbols. Note that"
|
|
262
|
+
"this option is not compatible with `--scie-pbs-free-threaded` or `--scie-pbs-debug`."
|
|
236
263
|
),
|
|
237
264
|
)
|
|
238
265
|
parser.add_argument(
|
|
@@ -318,6 +345,10 @@ def render_options(options):
|
|
|
318
345
|
if options.python_version:
|
|
319
346
|
args.append("--scie-python-version")
|
|
320
347
|
args.append(".".join(map(str, options.python_version)))
|
|
348
|
+
if options.pbs_free_threaded:
|
|
349
|
+
args.append("--scie-pbs-free-threaded")
|
|
350
|
+
if options.pbs_debug:
|
|
351
|
+
args.append("--scie-pbs-debug")
|
|
321
352
|
if options.pbs_stripped:
|
|
322
353
|
args.append("--scie-pbs-stripped")
|
|
323
354
|
for hash_algorithm in options.hash_algorithms:
|
|
@@ -398,6 +429,12 @@ def extract_options(options):
|
|
|
398
429
|
)
|
|
399
430
|
)
|
|
400
431
|
|
|
432
|
+
if options.scie_pbs_stripped and (options.scie_pbs_free_threaded or options.scie_pbs_debug):
|
|
433
|
+
raise ValueError(
|
|
434
|
+
"Python Standalone Builds does not release stripped distributions for debug or "
|
|
435
|
+
"free-threaded builds."
|
|
436
|
+
)
|
|
437
|
+
|
|
401
438
|
science_binary = None # type: Optional[Union[File, Url]]
|
|
402
439
|
if options.scie_science_binary:
|
|
403
440
|
url_info = urlparse.urlparse(options.scie_science_binary)
|
|
@@ -420,6 +457,8 @@ def extract_options(options):
|
|
|
420
457
|
pbs_release=options.scie_pbs_release,
|
|
421
458
|
pypy_release=options.scie_pypy_release,
|
|
422
459
|
python_version=python_version,
|
|
460
|
+
pbs_free_threaded=options.scie_pbs_free_threaded,
|
|
461
|
+
pbs_debug=options.scie_pbs_debug,
|
|
423
462
|
pbs_stripped=options.scie_pbs_stripped,
|
|
424
463
|
hash_algorithms=tuple(options.scie_hash_algorithms),
|
|
425
464
|
science_binary=science_binary,
|
pex/scie/model.py
CHANGED
|
@@ -303,6 +303,8 @@ class ScieOptions(object):
|
|
|
303
303
|
python_version = attr.ib(
|
|
304
304
|
default=None
|
|
305
305
|
) # type: Optional[Union[Tuple[int, int], Tuple[int, int, int]]]
|
|
306
|
+
pbs_free_threaded = attr.ib(default=False) # type: bool
|
|
307
|
+
pbs_debug = attr.ib(default=False) # type: bool
|
|
306
308
|
pbs_stripped = attr.ib(default=False) # type: bool
|
|
307
309
|
hash_algorithms = attr.ib(default=()) # type: Tuple[str, ...]
|
|
308
310
|
science_binary = attr.ib(default=None) # type: Optional[Union[File, Url]]
|
pex/scie/science.py
CHANGED
|
@@ -24,6 +24,7 @@ from pex.hashing import Sha256
|
|
|
24
24
|
from pex.os import is_exe
|
|
25
25
|
from pex.pep_440 import Version
|
|
26
26
|
from pex.pex import PEX
|
|
27
|
+
from pex.pex_info import PexInfo
|
|
27
28
|
from pex.result import Error, try_
|
|
28
29
|
from pex.scie.model import (
|
|
29
30
|
File,
|
|
@@ -37,6 +38,7 @@ from pex.scie.model import (
|
|
|
37
38
|
)
|
|
38
39
|
from pex.sysconfig import SysPlatform
|
|
39
40
|
from pex.third_party.packaging.specifiers import SpecifierSet
|
|
41
|
+
from pex.third_party.packaging.utils import parse_wheel_filename
|
|
40
42
|
from pex.third_party.packaging.version import InvalidVersion
|
|
41
43
|
from pex.tracer import TRACER
|
|
42
44
|
from pex.typing import TYPE_CHECKING
|
|
@@ -66,7 +68,7 @@ class Manifest(object):
|
|
|
66
68
|
|
|
67
69
|
|
|
68
70
|
SCIENCE_RELEASES_URL = "https://github.com/a-scie/lift/releases"
|
|
69
|
-
MIN_SCIENCE_VERSION = Version("0.
|
|
71
|
+
MIN_SCIENCE_VERSION = Version("0.15.1")
|
|
70
72
|
SCIENCE_REQUIREMENT = SpecifierSet("~={min_version}".format(min_version=MIN_SCIENCE_VERSION))
|
|
71
73
|
|
|
72
74
|
|
|
@@ -104,6 +106,16 @@ class Filenames(Enum["Filenames.Value"]):
|
|
|
104
106
|
Filenames.seal()
|
|
105
107
|
|
|
106
108
|
|
|
109
|
+
def _is_free_threaded_pex(pex_info):
|
|
110
|
+
# type: (PexInfo) -> bool
|
|
111
|
+
for distribution in pex_info.distributions:
|
|
112
|
+
_, _, _, tags = parse_wheel_filename(os.path.basename(distribution))
|
|
113
|
+
for tag in tags:
|
|
114
|
+
if tag.abi.startswith(("cp", "abi3")) and "t" in tag.abi:
|
|
115
|
+
return True
|
|
116
|
+
return False
|
|
117
|
+
|
|
118
|
+
|
|
107
119
|
def create_manifests(
|
|
108
120
|
configuration, # type: ScieConfiguration
|
|
109
121
|
name, # type: str
|
|
@@ -270,6 +282,7 @@ def create_manifests(
|
|
|
270
282
|
}
|
|
271
283
|
|
|
272
284
|
configure_binding_args = [Filenames.PEX.placeholder, Filenames.CONFIGURE_BINDING.placeholder]
|
|
285
|
+
pbs_free_threaded = _is_free_threaded_pex(pex_info) or configuration.options.pbs_free_threaded
|
|
273
286
|
for interpreter in configuration.interpreters:
|
|
274
287
|
lift = lift_template.copy()
|
|
275
288
|
|
|
@@ -285,10 +298,17 @@ def create_manifests(
|
|
|
285
298
|
interpreter.platform.qualified_file_name("{name}-lift.toml".format(name=name)),
|
|
286
299
|
)
|
|
287
300
|
|
|
301
|
+
version_str = interpreter.version_str
|
|
302
|
+
if Provider.PythonBuildStandalone is interpreter.provider:
|
|
303
|
+
if configuration.options.pbs_debug:
|
|
304
|
+
version_str += "d"
|
|
305
|
+
if pbs_free_threaded:
|
|
306
|
+
version_str += "t"
|
|
307
|
+
|
|
288
308
|
interpreter_config = {
|
|
289
309
|
"id": "python-distribution",
|
|
290
310
|
"provider": interpreter.provider.value,
|
|
291
|
-
"version":
|
|
311
|
+
"version": version_str,
|
|
292
312
|
"lazy": configuration.options.style is ScieStyle.LAZY,
|
|
293
313
|
}
|
|
294
314
|
if interpreter.release:
|
|
@@ -297,7 +317,9 @@ def create_manifests(
|
|
|
297
317
|
interpreter_config["base_url"] = "/".join(
|
|
298
318
|
(configuration.options.assets_base_url, "providers", str(interpreter.provider))
|
|
299
319
|
)
|
|
300
|
-
if Provider.PythonBuildStandalone is interpreter.provider
|
|
320
|
+
if Provider.PythonBuildStandalone is interpreter.provider and not (
|
|
321
|
+
configuration.options.pbs_debug or pbs_free_threaded
|
|
322
|
+
):
|
|
301
323
|
interpreter_config.update(
|
|
302
324
|
flavor=(
|
|
303
325
|
"install_only_stripped"
|
pex/sdist.py
ADDED
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
# Copyright 2025 Pex project contributors.
|
|
2
|
+
# Licensed under the Apache License, Version 2.0 (see LICENSE).
|
|
3
|
+
|
|
4
|
+
from __future__ import absolute_import
|
|
5
|
+
|
|
6
|
+
import copy
|
|
7
|
+
import os.path
|
|
8
|
+
import sys
|
|
9
|
+
import tarfile
|
|
10
|
+
from tarfile import TarInfo
|
|
11
|
+
|
|
12
|
+
from pex.compatibility import commonpath
|
|
13
|
+
from pex.typing import TYPE_CHECKING, cast
|
|
14
|
+
|
|
15
|
+
if TYPE_CHECKING:
|
|
16
|
+
from typing import Any, Dict, Optional, Text, TypeVar
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class FilterError(tarfile.TarError):
|
|
20
|
+
pass
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class AbsolutePathError(FilterError):
|
|
24
|
+
pass
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class OutsideDestinationError(FilterError):
|
|
28
|
+
pass
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class SpecialFileError(FilterError):
|
|
32
|
+
pass
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class AbsoluteLinkError(FilterError):
|
|
36
|
+
pass
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class LinkOutsideDestinationError(FilterError):
|
|
40
|
+
pass
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
_REALPATH_KWARGS = (
|
|
44
|
+
{"strict": getattr(os.path, "ALLOW_MISSING", False)} if sys.version_info[:2] >= (3, 10) else {}
|
|
45
|
+
) # type: Dict[str, Any]
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
if TYPE_CHECKING:
|
|
49
|
+
_Text = TypeVar("_Text", str, Text)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _realpath(path):
|
|
53
|
+
# type: (_Text) -> _Text
|
|
54
|
+
return os.path.realpath(path, **_REALPATH_KWARGS)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _get_filtered_attrs(
|
|
58
|
+
member, # type: TarInfo
|
|
59
|
+
dest_path, # type: Text
|
|
60
|
+
for_data=True, # type: bool
|
|
61
|
+
):
|
|
62
|
+
# type: (...) -> Dict[str, Any]
|
|
63
|
+
|
|
64
|
+
# N.B.: Copied from CPython 3.14 stdlib tarfile.py
|
|
65
|
+
# Modifications:
|
|
66
|
+
# + Exception types replicated with error messages placed at call site.
|
|
67
|
+
# + `os.path.realpath` -> `_realpath` to deal with `strict` parameter.
|
|
68
|
+
# + `os.path.commonpath` -> `pex.compatibility.commonpath`
|
|
69
|
+
# + `mode = None` guarded by `sys.version_info[:2] >= (3, 12)` with commentary.
|
|
70
|
+
# + `{uid,gid,uname,gname} = None` guarded by `sys.version_info[:2] >= (3, 12)` with commentary.
|
|
71
|
+
|
|
72
|
+
new_attrs = {} # type: Dict[str, Any]
|
|
73
|
+
name = member.name
|
|
74
|
+
dest_path = _realpath(dest_path)
|
|
75
|
+
# Strip leading / (tar's directory separator) from filenames.
|
|
76
|
+
# Include os.sep (target OS directory separator) as well.
|
|
77
|
+
if name.startswith(("/", os.sep)):
|
|
78
|
+
name = new_attrs["name"] = member.path.lstrip("/" + os.sep)
|
|
79
|
+
if os.path.isabs(name):
|
|
80
|
+
# Path is absolute even after stripping.
|
|
81
|
+
# For example, 'C:/foo' on Windows.
|
|
82
|
+
raise AbsolutePathError("member {name!r} has an absolute path".format(name=member.name))
|
|
83
|
+
# Ensure we stay in the destination
|
|
84
|
+
target_path = _realpath(os.path.join(dest_path, name))
|
|
85
|
+
if commonpath([target_path, dest_path]) != dest_path:
|
|
86
|
+
raise OutsideDestinationError(
|
|
87
|
+
"{name!r} would be extracted to {path!r}, which is outside the destination".format(
|
|
88
|
+
name=member.name, path=target_path
|
|
89
|
+
)
|
|
90
|
+
)
|
|
91
|
+
# Limit permissions (no high bits, and go-w)
|
|
92
|
+
mode = member.mode # type: Optional[int]
|
|
93
|
+
if mode is not None:
|
|
94
|
+
# Strip high bits & group/other write bits
|
|
95
|
+
mode = mode & 0o755
|
|
96
|
+
if for_data:
|
|
97
|
+
# For data, handle permissions & file types
|
|
98
|
+
if member.isreg() or member.islnk():
|
|
99
|
+
if not mode & 0o100:
|
|
100
|
+
# Clear executable bits if not executable by user
|
|
101
|
+
mode &= ~0o111
|
|
102
|
+
# Ensure owner can read & write
|
|
103
|
+
mode |= 0o600
|
|
104
|
+
elif member.isdir() or member.issym():
|
|
105
|
+
if sys.version_info[:2] >= (3, 12):
|
|
106
|
+
# Ignore mode for directories & symlinks
|
|
107
|
+
mode = None
|
|
108
|
+
else:
|
|
109
|
+
# Retain stripped mode since older Pythons do not support None.
|
|
110
|
+
pass
|
|
111
|
+
else:
|
|
112
|
+
# Reject special files
|
|
113
|
+
raise SpecialFileError("{name!r} is a special file".format(name=member.name))
|
|
114
|
+
if mode != member.mode:
|
|
115
|
+
new_attrs["mode"] = mode
|
|
116
|
+
if for_data:
|
|
117
|
+
if sys.version_info[:2] >= (3, 12):
|
|
118
|
+
# Ignore ownership for 'data'
|
|
119
|
+
if member.uid is not None:
|
|
120
|
+
new_attrs["uid"] = None
|
|
121
|
+
if member.gid is not None:
|
|
122
|
+
new_attrs["gid"] = None
|
|
123
|
+
if member.uname is not None:
|
|
124
|
+
new_attrs["uname"] = None
|
|
125
|
+
if member.gname is not None:
|
|
126
|
+
new_attrs["gname"] = None
|
|
127
|
+
else:
|
|
128
|
+
# Retain uid/gid/uname/gname since older Pythons do not support None.
|
|
129
|
+
pass
|
|
130
|
+
|
|
131
|
+
# Check link destination for 'data'
|
|
132
|
+
if member.islnk() or member.issym():
|
|
133
|
+
if os.path.isabs(member.linkname):
|
|
134
|
+
raise AbsoluteLinkError(
|
|
135
|
+
"{name!r} is a link to an absolute path".format(name=member.name)
|
|
136
|
+
)
|
|
137
|
+
normalized = os.path.normpath(member.linkname)
|
|
138
|
+
if normalized != member.linkname:
|
|
139
|
+
new_attrs["linkname"] = normalized
|
|
140
|
+
if member.issym():
|
|
141
|
+
target_path = os.path.join(dest_path, os.path.dirname(name), member.linkname)
|
|
142
|
+
else:
|
|
143
|
+
target_path = os.path.join(dest_path, member.linkname)
|
|
144
|
+
target_path = _realpath(target_path)
|
|
145
|
+
if commonpath([target_path, dest_path]) != dest_path:
|
|
146
|
+
raise LinkOutsideDestinationError(
|
|
147
|
+
"{name!r} would link to {path!r}, which is outside the destination".format(
|
|
148
|
+
name=member.name, path=target_path
|
|
149
|
+
)
|
|
150
|
+
)
|
|
151
|
+
return new_attrs
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def _replace(
|
|
155
|
+
member, # type: TarInfo
|
|
156
|
+
attrs, # type: Dict[str, Any]
|
|
157
|
+
):
|
|
158
|
+
# type: (...) -> TarInfo
|
|
159
|
+
|
|
160
|
+
replace = getattr(member, "replace", None)
|
|
161
|
+
if replace:
|
|
162
|
+
attrs["deep"] = False
|
|
163
|
+
return cast(TarInfo, replace(**attrs))
|
|
164
|
+
|
|
165
|
+
result = copy.copy(member)
|
|
166
|
+
for attr, value in attrs.items():
|
|
167
|
+
setattr(result, attr, value)
|
|
168
|
+
return result
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def _data_filter(
|
|
172
|
+
member, # type: TarInfo
|
|
173
|
+
dest_path, # type: Text
|
|
174
|
+
):
|
|
175
|
+
# type: (...) -> TarInfo
|
|
176
|
+
new_attrs = _get_filtered_attrs(member, dest_path, True)
|
|
177
|
+
if new_attrs:
|
|
178
|
+
return _replace(member, new_attrs)
|
|
179
|
+
return member
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
_EXTRACTALL_DATA_FILTER_KWARGS = {"filter": "data"} # type: Dict[str, Any]
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
class InvalidSourceDistributionError(ValueError):
|
|
186
|
+
pass
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def extract_tarball(
|
|
190
|
+
tarball_path, # type: Text
|
|
191
|
+
dest_dir, # type: _Text
|
|
192
|
+
):
|
|
193
|
+
# type: (...) -> _Text
|
|
194
|
+
|
|
195
|
+
with tarfile.open(tarball_path) as tf:
|
|
196
|
+
if sys.version_info[:2] >= (3, 12):
|
|
197
|
+
tf.extractall(dest_dir, **_EXTRACTALL_DATA_FILTER_KWARGS)
|
|
198
|
+
else:
|
|
199
|
+
for tar_info in tf: # type: ignore[unreachable]
|
|
200
|
+
tar_info = _data_filter(tar_info, dest_dir)
|
|
201
|
+
tf.extract(tar_info, dest_dir)
|
|
202
|
+
|
|
203
|
+
listing = os.listdir(dest_dir)
|
|
204
|
+
if len(listing) != 1:
|
|
205
|
+
raise InvalidSourceDistributionError(
|
|
206
|
+
"Expected one top-level project directory to be extracted from {project}, "
|
|
207
|
+
"found {count}: {listing}".format(
|
|
208
|
+
project=tarball_path, count=len(listing), listing=", ".join(listing)
|
|
209
|
+
)
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
project_dir = os.path.join(dest_dir, listing[0])
|
|
213
|
+
if not os.path.isdir(project_dir):
|
|
214
|
+
raise InvalidSourceDistributionError(
|
|
215
|
+
"Expected one top-level project directory to be extracted from {project}, "
|
|
216
|
+
"found file: {path}".format(project=tarball_path, path=listing[0])
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
return project_dir
|
pex/sh_boot.py
CHANGED
|
@@ -11,14 +11,16 @@ from textwrap import dedent
|
|
|
11
11
|
from pex import dist_metadata, variables
|
|
12
12
|
from pex.compatibility import shlex_quote
|
|
13
13
|
from pex.dist_metadata import Distribution
|
|
14
|
-
from pex.interpreter import PythonInterpreter
|
|
14
|
+
from pex.interpreter import PythonInterpreter
|
|
15
15
|
from pex.interpreter_constraints import InterpreterConstraints, iter_compatible_versions
|
|
16
|
+
from pex.interpreter_implementation import InterpreterImplementation
|
|
16
17
|
from pex.layout import Layout
|
|
17
18
|
from pex.orderedset import OrderedSet
|
|
18
19
|
from pex.os import WINDOWS
|
|
19
20
|
from pex.pep_440 import Version
|
|
20
21
|
from pex.pex_info import PexInfo
|
|
21
22
|
from pex.targets import Targets
|
|
23
|
+
from pex.third_party.packaging.specifiers import SpecifierSet
|
|
22
24
|
from pex.typing import TYPE_CHECKING
|
|
23
25
|
from pex.version import __version__
|
|
24
26
|
|
|
@@ -32,14 +34,12 @@ else:
|
|
|
32
34
|
|
|
33
35
|
@attr.s(frozen=True)
|
|
34
36
|
class PythonBinaryName(object):
|
|
35
|
-
|
|
37
|
+
implementation = attr.ib() # type: InterpreterImplementation.Value
|
|
36
38
|
version = attr.ib() # type: Tuple[int, ...]
|
|
37
39
|
|
|
38
40
|
def render(self, version_components=2):
|
|
39
41
|
# type: (int) -> str
|
|
40
|
-
return
|
|
41
|
-
name=self.name, version=".".join(map(str, self.version[:version_components]))
|
|
42
|
-
)
|
|
42
|
+
return self.implementation.calculate_binary_name(self.version[:version_components])
|
|
43
43
|
|
|
44
44
|
|
|
45
45
|
def _calculate_applicable_binary_names(
|
|
@@ -55,17 +55,15 @@ def _calculate_applicable_binary_names(
|
|
|
55
55
|
ic_majors_minors = OrderedSet() # type: OrderedSet[PythonBinaryName]
|
|
56
56
|
if interpreter_constraints:
|
|
57
57
|
ic_majors_minors.update(
|
|
58
|
-
PythonBinaryName(
|
|
59
|
-
name=calculate_binary_name(platform_python_implementation=name), version=version
|
|
60
|
-
)
|
|
58
|
+
PythonBinaryName(implementation=implementation, version=version)
|
|
61
59
|
for interpreter_constraint in interpreter_constraints
|
|
62
60
|
for version in iter_compatible_versions(
|
|
63
|
-
requires_python=[
|
|
61
|
+
requires_python=[interpreter_constraint.specifier]
|
|
64
62
|
)
|
|
65
|
-
for
|
|
66
|
-
(interpreter_constraint.
|
|
67
|
-
if interpreter_constraint.
|
|
68
|
-
else (
|
|
63
|
+
for implementation in (
|
|
64
|
+
(interpreter_constraint.implementation,)
|
|
65
|
+
if interpreter_constraint.implementation
|
|
66
|
+
else InterpreterImplementation.values()
|
|
69
67
|
)
|
|
70
68
|
)
|
|
71
69
|
# If we get targets from ICs, we only want explicitly specified local interpreter targets;
|
|
@@ -75,10 +73,10 @@ def _calculate_applicable_binary_names(
|
|
|
75
73
|
names = OrderedSet() # type: OrderedSet[PythonBinaryName]
|
|
76
74
|
# 1. Explicit targets 1st.
|
|
77
75
|
for target in targets.unique_targets(only_explicit=only_explicit):
|
|
78
|
-
if target.python_version is not None:
|
|
76
|
+
if target.implementation and target.python_version is not None:
|
|
79
77
|
names.add(
|
|
80
78
|
PythonBinaryName(
|
|
81
|
-
|
|
79
|
+
implementation=target.implementation, version=target.python_version
|
|
82
80
|
)
|
|
83
81
|
)
|
|
84
82
|
|
|
@@ -89,10 +87,14 @@ def _calculate_applicable_binary_names(
|
|
|
89
87
|
# more sophisticated detection and re-direction from these during its own bootstrap. When doing
|
|
90
88
|
# so, select these interpreters from newest to oldest since it more likely any given machine
|
|
91
89
|
# will have Python 3 at this point than it will Python 2.
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
90
|
+
pex_requires_python_override = os.environ.get("_PEX_REQUIRES_PYTHON", None)
|
|
91
|
+
if pex_requires_python_override:
|
|
92
|
+
pex_requires_python = SpecifierSet(pex_requires_python_override)
|
|
93
|
+
else:
|
|
94
|
+
pex_requires_python = SpecifierSet(">=2.7")
|
|
95
|
+
dist = dist_metadata.find_distribution("pex") # type: Optional[Distribution]
|
|
96
|
+
if dist and dist.metadata.version == Version(__version__):
|
|
97
|
+
pex_requires_python = dist.metadata.requires_python
|
|
96
98
|
pex_supported_python_versions = tuple(
|
|
97
99
|
reversed(list(iter_compatible_versions(requires_python=[pex_requires_python])))
|
|
98
100
|
)
|
|
@@ -102,11 +104,12 @@ def _calculate_applicable_binary_names(
|
|
|
102
104
|
# for CPython end targets and for PyPy it need not be quite as fast since it inherently asks you
|
|
103
105
|
# to trade startup latency for longer term jit performance.
|
|
104
106
|
names.update(
|
|
105
|
-
PythonBinaryName(
|
|
107
|
+
PythonBinaryName(implementation=InterpreterImplementation.CPYTHON, version=version)
|
|
106
108
|
for version in pex_supported_python_versions
|
|
107
109
|
)
|
|
108
110
|
names.update(
|
|
109
|
-
PythonBinaryName(
|
|
111
|
+
PythonBinaryName(implementation=InterpreterImplementation.PYPY, version=version)
|
|
112
|
+
for version in pex_supported_python_versions
|
|
110
113
|
)
|
|
111
114
|
|
|
112
115
|
# Favor more specific interpreter names since these should need re-direction less often.
|
pex/sysconfig.py
CHANGED
|
@@ -82,7 +82,7 @@ class _PlatformValue(Enum.Value):
|
|
|
82
82
|
self.arch = arch
|
|
83
83
|
|
|
84
84
|
@property
|
|
85
|
-
def
|
|
85
|
+
def exe_extension(self):
|
|
86
86
|
# type: () -> str
|
|
87
87
|
return ".exe" if self.os is Os.WINDOWS else ""
|
|
88
88
|
|
|
@@ -93,12 +93,14 @@ class _PlatformValue(Enum.Value):
|
|
|
93
93
|
|
|
94
94
|
def binary_name(self, binary_name):
|
|
95
95
|
# type: (_Text) -> _Text
|
|
96
|
-
return "{binary_name}{extension}".format(
|
|
96
|
+
return "{binary_name}{extension}".format(
|
|
97
|
+
binary_name=binary_name, extension=self.exe_extension
|
|
98
|
+
)
|
|
97
99
|
|
|
98
100
|
def qualified_binary_name(self, binary_name):
|
|
99
101
|
# type: (_Text) -> _Text
|
|
100
102
|
return "{binary_name}-{platform}{extension}".format(
|
|
101
|
-
binary_name=binary_name, platform=self, extension=self.
|
|
103
|
+
binary_name=binary_name, platform=self, extension=self.exe_extension
|
|
102
104
|
)
|
|
103
105
|
|
|
104
106
|
def qualified_file_name(self, file_name):
|
pex/targets.py
CHANGED
|
@@ -5,8 +5,9 @@ from __future__ import absolute_import
|
|
|
5
5
|
|
|
6
6
|
import os
|
|
7
7
|
|
|
8
|
-
from pex.dist_metadata import
|
|
9
|
-
from pex.interpreter import PythonInterpreter
|
|
8
|
+
from pex.dist_metadata import Constraint, Distribution
|
|
9
|
+
from pex.interpreter import PythonInterpreter
|
|
10
|
+
from pex.interpreter_implementation import InterpreterImplementation
|
|
10
11
|
from pex.orderedset import OrderedSet
|
|
11
12
|
from pex.pep_425 import CompatibilityTags, RankedTag
|
|
12
13
|
from pex.pep_508 import MarkerEnvironment
|
|
@@ -48,14 +49,25 @@ class Target(object):
|
|
|
48
49
|
id = attr.ib() # type: str
|
|
49
50
|
platform = attr.ib() # type: Platform
|
|
50
51
|
marker_environment = attr.ib() # type: MarkerEnvironment
|
|
52
|
+
implementation = attr.ib(init=False) # type: Optional[InterpreterImplementation.Value]
|
|
53
|
+
|
|
54
|
+
def __attrs_post_init__(self):
|
|
55
|
+
interpreter_implementation = None # type: Optional[InterpreterImplementation.Value]
|
|
56
|
+
for interpreter_impl in InterpreterImplementation.values():
|
|
57
|
+
if interpreter_impl.value == self.marker_environment.platform_python_implementation:
|
|
58
|
+
interpreter_implementation = interpreter_impl
|
|
59
|
+
break
|
|
60
|
+
object.__setattr__(self, "implementation", interpreter_implementation)
|
|
51
61
|
|
|
52
62
|
def binary_name(self, version_components=2):
|
|
53
63
|
# type: (int) -> str
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
64
|
+
interpreter_implementation = self.implementation or InterpreterImplementation.CPYTHON
|
|
65
|
+
return interpreter_implementation.calculate_binary_name(
|
|
66
|
+
version=(
|
|
67
|
+
self.python_version[:version_components]
|
|
68
|
+
if self.python_version and version_components > 0
|
|
69
|
+
else None
|
|
70
|
+
)
|
|
59
71
|
)
|
|
60
72
|
|
|
61
73
|
@property
|
|
@@ -145,7 +157,7 @@ class Target(object):
|
|
|
145
157
|
|
|
146
158
|
def requirement_applies(
|
|
147
159
|
self,
|
|
148
|
-
requirement, # type:
|
|
160
|
+
requirement, # type: Constraint
|
|
149
161
|
extras=(), # type: Iterable[str]
|
|
150
162
|
):
|
|
151
163
|
# type: (...) -> bool
|
|
@@ -171,7 +183,7 @@ class Target(object):
|
|
|
171
183
|
|
|
172
184
|
def wheel_applies(self, wheel):
|
|
173
185
|
# type: (Distribution) -> WheelEvaluation
|
|
174
|
-
wheel_tags = CompatibilityTags.from_wheel(wheel
|
|
186
|
+
wheel_tags = CompatibilityTags.from_wheel(wheel)
|
|
175
187
|
ranked_tag = self.supported_tags.best_match(wheel_tags)
|
|
176
188
|
return WheelEvaluation(
|
|
177
189
|
tags=tuple(wheel_tags),
|
|
@@ -193,6 +205,7 @@ class Target(object):
|
|
|
193
205
|
return str(self.platform.tag)
|
|
194
206
|
|
|
195
207
|
def render_description(self):
|
|
208
|
+
# type: () -> str
|
|
196
209
|
raise NotImplementedError()
|
|
197
210
|
|
|
198
211
|
def __repr__(self):
|
|
@@ -247,6 +260,7 @@ class LocalInterpreter(Target):
|
|
|
247
260
|
return self.interpreter.binary
|
|
248
261
|
|
|
249
262
|
def render_description(self):
|
|
263
|
+
# type: () -> str
|
|
250
264
|
return "{platform} interpreter at {path}".format(
|
|
251
265
|
platform=self.interpreter.platform.tag, path=self.interpreter.binary
|
|
252
266
|
)
|
|
@@ -269,6 +283,7 @@ class AbbreviatedPlatform(Target):
|
|
|
269
283
|
return self.platform.supported_tags
|
|
270
284
|
|
|
271
285
|
def render_description(self):
|
|
286
|
+
# type: () -> str
|
|
272
287
|
return "abbreviated platform {platform}".format(platform=self.platform.tag)
|
|
273
288
|
|
|
274
289
|
|
|
@@ -311,6 +326,7 @@ class CompletePlatform(Target):
|
|
|
311
326
|
return self._supported_tags
|
|
312
327
|
|
|
313
328
|
def render_description(self):
|
|
329
|
+
# type: () -> str
|
|
314
330
|
return "complete platform {platform}".format(platform=self.platform.tag)
|
|
315
331
|
|
|
316
332
|
|
|
@@ -330,6 +346,11 @@ class Targets(object):
|
|
|
330
346
|
complete_platforms = attr.ib(default=()) # type: Tuple[CompletePlatform, ...]
|
|
331
347
|
platforms = attr.ib(default=()) # type: Tuple[Optional[Platform], ...]
|
|
332
348
|
|
|
349
|
+
@property
|
|
350
|
+
def is_empty(self):
|
|
351
|
+
# type: () -> bool
|
|
352
|
+
return not self.interpreters and not self.complete_platforms and not self.platforms
|
|
353
|
+
|
|
333
354
|
@property
|
|
334
355
|
def interpreter(self):
|
|
335
356
|
# type: () -> Optional[PythonInterpreter]
|
|
@@ -391,7 +412,7 @@ class Targets(object):
|
|
|
391
412
|
|
|
392
413
|
def require_at_most_one_target(self, purpose):
|
|
393
414
|
# type: (str) -> Union[Optional[Target], Error]
|
|
394
|
-
resolved_targets = self.unique_targets(only_explicit=
|
|
415
|
+
resolved_targets = self.unique_targets(only_explicit=True)
|
|
395
416
|
if len(resolved_targets) > 1:
|
|
396
417
|
return Error(
|
|
397
418
|
"At most a single target is required for {purpose}.\n"
|
pex/third_party/__init__.py
CHANGED
|
@@ -639,7 +639,7 @@ def expose_installed_wheels(
|
|
|
639
639
|
|
|
640
640
|
from pex.atomic_directory import atomic_directory
|
|
641
641
|
from pex.cache.dirs import InstalledWheelDir
|
|
642
|
-
from pex.
|
|
642
|
+
from pex.installed_wheel import InstalledWheel
|
|
643
643
|
|
|
644
644
|
for path in expose(dists, interpreter=interpreter):
|
|
645
645
|
# TODO(John Sirois): Maybe consolidate with pex.resolver.BuildAndInstallRequest.
|