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/environment.py
CHANGED
|
@@ -15,6 +15,7 @@ from pex.dependency_configuration import DependencyConfiguration
|
|
|
15
15
|
from pex.dist_metadata import Distribution, Requirement, is_wheel
|
|
16
16
|
from pex.fingerprinted_distribution import FingerprintedDistribution
|
|
17
17
|
from pex.inherit_path import InheritPath
|
|
18
|
+
from pex.installed_wheel import InstalledWheel
|
|
18
19
|
from pex.interpreter import PythonInterpreter
|
|
19
20
|
from pex.layout import ensure_installed, identify_layout
|
|
20
21
|
from pex.orderedset import OrderedSet
|
|
@@ -26,6 +27,7 @@ from pex.third_party.packaging import specifiers
|
|
|
26
27
|
from pex.third_party.packaging.tags import Tag
|
|
27
28
|
from pex.tracer import TRACER
|
|
28
29
|
from pex.typing import TYPE_CHECKING
|
|
30
|
+
from pex.whl import repacked_whl
|
|
29
31
|
|
|
30
32
|
if TYPE_CHECKING:
|
|
31
33
|
from typing import (
|
|
@@ -66,6 +68,22 @@ def _import_pkg_resources():
|
|
|
66
68
|
return None, False
|
|
67
69
|
|
|
68
70
|
|
|
71
|
+
def _fd_lt(
|
|
72
|
+
self, # type: FingerprintedDistribution
|
|
73
|
+
other, # type: FingerprintedDistribution
|
|
74
|
+
):
|
|
75
|
+
# type: (...) -> bool
|
|
76
|
+
if self.project_name.normalized < other.project_name.normalized:
|
|
77
|
+
return True
|
|
78
|
+
|
|
79
|
+
# Since we want to rank higher versions higher (earlier) we need to reverse the natural
|
|
80
|
+
# ordering of Version in Distribution which is least to greatest.
|
|
81
|
+
if self.distribution.metadata.version >= other.distribution.metadata.version:
|
|
82
|
+
return True
|
|
83
|
+
|
|
84
|
+
return self.fingerprint < other.fingerprint
|
|
85
|
+
|
|
86
|
+
|
|
69
87
|
@attr.s(frozen=True)
|
|
70
88
|
class _RankedDistribution(object):
|
|
71
89
|
# N.B.: A distribution implements rich comparison with the leading component being the
|
|
@@ -77,9 +95,7 @@ class _RankedDistribution(object):
|
|
|
77
95
|
# The attr project type stub file simply misses this.
|
|
78
96
|
_fd_cmp = attr.cmp_using( # type: ignore[attr-defined]
|
|
79
97
|
eq=FingerprintedDistribution.__eq__,
|
|
80
|
-
|
|
81
|
-
# ordering of Version in Distribution which is least to greatest.
|
|
82
|
-
lt=FingerprintedDistribution.__ge__,
|
|
98
|
+
lt=_fd_lt,
|
|
83
99
|
)
|
|
84
100
|
|
|
85
101
|
@classmethod
|
|
@@ -291,12 +307,7 @@ class PEXEnvironment(object):
|
|
|
291
307
|
|
|
292
308
|
def iter_distributions(self, result_type_wheel_file=False):
|
|
293
309
|
# type: (bool) -> Iterator[FingerprintedDistribution]
|
|
294
|
-
if result_type_wheel_file:
|
|
295
|
-
if not self._pex_info.deps_are_wheel_files:
|
|
296
|
-
raise ResolveError(
|
|
297
|
-
"Cannot resolve .whl files from PEX at {pex}; its dependencies are in the "
|
|
298
|
-
"form of pre-installed wheel chroots.".format(pex=self.source_pex)
|
|
299
|
-
)
|
|
310
|
+
if result_type_wheel_file and self._pex_info.deps_are_wheel_files:
|
|
300
311
|
with TRACER.timed(
|
|
301
312
|
"Searching dependency cache: {cache}".format(
|
|
302
313
|
cache=os.path.join(self.source_pex, self._pex_info.internal_cache)
|
|
@@ -320,10 +331,17 @@ class PEXEnvironment(object):
|
|
|
320
331
|
):
|
|
321
332
|
for distribution_name, fingerprint in self._pex_info.distributions.items():
|
|
322
333
|
dist_path = os.path.join(internal_cache, distribution_name)
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
334
|
+
if result_type_wheel_file:
|
|
335
|
+
yield repacked_whl(
|
|
336
|
+
installed_wheel=dist_path,
|
|
337
|
+
distribution_name=distribution_name,
|
|
338
|
+
fingerprint=fingerprint,
|
|
339
|
+
use_system_time=True,
|
|
340
|
+
)
|
|
341
|
+
else:
|
|
342
|
+
yield FingerprintedDistribution(
|
|
343
|
+
distribution=Distribution.load(dist_path), fingerprint=fingerprint
|
|
344
|
+
)
|
|
327
345
|
|
|
328
346
|
def _update_candidate_distributions(self, distribution_iter):
|
|
329
347
|
# type: (Iterable[FingerprintedDistribution]) -> None
|
|
@@ -768,10 +786,10 @@ class PEXEnvironment(object):
|
|
|
768
786
|
current_interpreter = PythonInterpreter.get()
|
|
769
787
|
pex_warnings.warn(
|
|
770
788
|
"The legacy `pkg_resources` package cannot be imported by the "
|
|
771
|
-
"{
|
|
789
|
+
"{implementation} {version} interpreter at {path}.\n"
|
|
772
790
|
"The following distributions need `pkg_resources` to load some legacy "
|
|
773
791
|
"namespace packages and may fail to work properly:\n{dists}".format(
|
|
774
|
-
|
|
792
|
+
implementation=current_interpreter.identity.implementation,
|
|
775
793
|
version=current_interpreter.python,
|
|
776
794
|
path=current_interpreter.binary,
|
|
777
795
|
dists=dists,
|
|
@@ -803,22 +821,25 @@ class PEXEnvironment(object):
|
|
|
803
821
|
if dist.location in sys.path:
|
|
804
822
|
continue
|
|
805
823
|
with TRACER.timed("Activating %s" % dist, V=2):
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
+
for entry in InstalledWheel.load(dist.location).iter_sys_path_entries():
|
|
825
|
+
if self._pex_info.inherit_path == InheritPath.FALLBACK:
|
|
826
|
+
# Prepend location to sys.path.
|
|
827
|
+
#
|
|
828
|
+
# This ensures that bundled versions of libraries will be used before
|
|
829
|
+
# system-installed versions, in case something is installed in both, helping
|
|
830
|
+
# to favor hermeticity in the case of non-hermetic PEX files (i.e. those
|
|
831
|
+
# with inherit_path=True).
|
|
832
|
+
#
|
|
833
|
+
# If the path is not already in sys.path, site.addsitedir will append (not
|
|
834
|
+
# prepend) the path to sys.path. But if the path is already in sys.path,
|
|
835
|
+
# site.addsitedir will leave sys.path unmodified, but will do everything
|
|
836
|
+
# else it would do. This is not part of its advertised contract (which is
|
|
837
|
+
# very vague), but has been verified to be the case by inspecting its source
|
|
838
|
+
# for both cpython 2.7 and cpython 3.7.
|
|
839
|
+
sys.path.insert(0, entry)
|
|
840
|
+
else:
|
|
841
|
+
sys.path.append(entry)
|
|
842
|
+
|
|
843
|
+
with TRACER.timed("Adding sitedir", V=2):
|
|
844
|
+
site.addsitedir(entry)
|
|
824
845
|
return resolved
|
pex/finders.py
CHANGED
|
@@ -15,7 +15,7 @@ from pex.dist_metadata import (
|
|
|
15
15
|
NamedEntryPoint,
|
|
16
16
|
)
|
|
17
17
|
from pex.executables import is_python_script
|
|
18
|
-
from pex.
|
|
18
|
+
from pex.installed_wheel import InstalledWheel
|
|
19
19
|
from pex.pep_503 import ProjectName
|
|
20
20
|
from pex.sysconfig import SCRIPT_DIR, script_name
|
|
21
21
|
from pex.typing import TYPE_CHECKING, cast
|
pex/hashing.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Copyright 2022 Pex project contributors.
|
|
2
2
|
# Licensed under the Apache License, Version 2.0 (see LICENSE).
|
|
3
3
|
|
|
4
|
-
from __future__ import absolute_import
|
|
4
|
+
from __future__ import absolute_import, print_function
|
|
5
5
|
|
|
6
6
|
import hashlib
|
|
7
7
|
import os
|
|
@@ -241,10 +241,12 @@ def dir_hash(
|
|
|
241
241
|
def iter_files():
|
|
242
242
|
# type: () -> Iterator[Text]
|
|
243
243
|
for root, dirs, files in os.walk(top, followlinks=True):
|
|
244
|
-
dirs[:] = [
|
|
244
|
+
dirs[:] = [
|
|
245
|
+
d for d in dirs if dir_filter(os.path.relpath(os.path.join(root, d), top))
|
|
246
|
+
]
|
|
245
247
|
for f in files:
|
|
246
248
|
path = os.path.join(root, f)
|
|
247
|
-
if file_filter(path):
|
|
249
|
+
if file_filter(os.path.relpath(path, top)):
|
|
248
250
|
yield path
|
|
249
251
|
|
|
250
252
|
file_paths = sorted(iter_files())
|
|
@@ -278,16 +280,26 @@ def zip_hash(
|
|
|
278
280
|
else zf.namelist()
|
|
279
281
|
)
|
|
280
282
|
|
|
281
|
-
dirs =
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
283
|
+
dirs = set()
|
|
284
|
+
for name in namelist:
|
|
285
|
+
if name.endswith("/"):
|
|
286
|
+
dirname = name.rstrip("/")
|
|
287
|
+
else:
|
|
288
|
+
dirname = os.path.dirname(name)
|
|
289
|
+
while dirname:
|
|
290
|
+
dirs.add(dirname)
|
|
291
|
+
dirname = os.path.dirname(dirname)
|
|
292
|
+
|
|
293
|
+
accept_dirs = frozenset(
|
|
294
|
+
d for d in dirs if dir_filter(os.path.relpath(d, relpath) if relpath else d)
|
|
295
|
+
)
|
|
296
|
+
reject_dirs = tuple("{dir}/".format(dir=path) for path in (dirs - accept_dirs))
|
|
285
297
|
accept_files = sorted(
|
|
286
298
|
name
|
|
287
299
|
for name in namelist
|
|
288
300
|
if not name.endswith("/")
|
|
289
|
-
and not
|
|
290
|
-
and file_filter(os.path.
|
|
301
|
+
and not name.startswith(reject_dirs)
|
|
302
|
+
and file_filter(os.path.relpath(name, relpath) if relpath else name)
|
|
291
303
|
)
|
|
292
304
|
|
|
293
305
|
hashed_names = (
|
|
@@ -297,3 +309,53 @@ def zip_hash(
|
|
|
297
309
|
|
|
298
310
|
for filename in accept_files:
|
|
299
311
|
update_hash(zf.open(filename, "r"), digest)
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
if __name__ == "__main__":
|
|
315
|
+
import sys
|
|
316
|
+
import zipfile
|
|
317
|
+
from argparse import ArgumentParser
|
|
318
|
+
|
|
319
|
+
from pex.common import is_pyc_dir, is_pyc_file
|
|
320
|
+
|
|
321
|
+
parser = ArgumentParser()
|
|
322
|
+
parser.add_argument(
|
|
323
|
+
"--exclude-dir",
|
|
324
|
+
dest="exclude_dirs",
|
|
325
|
+
action="append",
|
|
326
|
+
default=[],
|
|
327
|
+
)
|
|
328
|
+
parser.add_argument("--zip-relpath")
|
|
329
|
+
parser.add_argument("paths", nargs="+")
|
|
330
|
+
|
|
331
|
+
options = parser.parse_args()
|
|
332
|
+
exclude_dirs = frozenset(options.exclude_dirs)
|
|
333
|
+
|
|
334
|
+
for path in options.paths:
|
|
335
|
+
digest = Sha256()
|
|
336
|
+
if zipfile.is_zipfile(path):
|
|
337
|
+
zip_hash(
|
|
338
|
+
path,
|
|
339
|
+
digest=digest,
|
|
340
|
+
relpath=options.zip_relpath,
|
|
341
|
+
dir_filter=(
|
|
342
|
+
lambda dir_path: (
|
|
343
|
+
not is_pyc_dir(dir_path) and os.path.basename(dir_path) not in exclude_dirs
|
|
344
|
+
)
|
|
345
|
+
),
|
|
346
|
+
file_filter=lambda f: not is_pyc_file(f),
|
|
347
|
+
)
|
|
348
|
+
elif os.path.isdir(path):
|
|
349
|
+
dir_hash(
|
|
350
|
+
path,
|
|
351
|
+
digest=digest,
|
|
352
|
+
dir_filter=(
|
|
353
|
+
lambda dir_path: (
|
|
354
|
+
not is_pyc_dir(dir_path) and os.path.basename(dir_path) not in exclude_dirs
|
|
355
|
+
)
|
|
356
|
+
),
|
|
357
|
+
file_filter=lambda f: not is_pyc_file(f),
|
|
358
|
+
)
|
|
359
|
+
else:
|
|
360
|
+
print("Can only hash zip files or directories. Skipping file", path, file=sys.stderr)
|
|
361
|
+
print(path, digest.hexdigest(), file=sys.stdout)
|
pex/installed_wheel.py
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
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 hashlib
|
|
7
|
+
import json
|
|
8
|
+
import os
|
|
9
|
+
|
|
10
|
+
from pex.typing import TYPE_CHECKING, cast
|
|
11
|
+
from pex.util import CacheHelper
|
|
12
|
+
from pex.wheel import WHEEL, Wheel, WheelMetadataLoadError
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from typing import Iterator, Optional, Text, Tuple
|
|
16
|
+
|
|
17
|
+
import attr # vendor:skip
|
|
18
|
+
else:
|
|
19
|
+
import pex.third_party.attr as attr
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@attr.s(frozen=True)
|
|
23
|
+
class InstalledWheel(object):
|
|
24
|
+
class LoadError(Exception):
|
|
25
|
+
"""Indicates an installed wheel was not loadable at a particular path."""
|
|
26
|
+
|
|
27
|
+
_LAYOUT_JSON_FILENAME = ".layout.json"
|
|
28
|
+
|
|
29
|
+
@classmethod
|
|
30
|
+
def layout_file(cls, prefix_dir):
|
|
31
|
+
# type: (str) -> str
|
|
32
|
+
return os.path.join(prefix_dir, cls._LAYOUT_JSON_FILENAME)
|
|
33
|
+
|
|
34
|
+
@classmethod
|
|
35
|
+
def save(
|
|
36
|
+
cls,
|
|
37
|
+
prefix_dir, # type: str
|
|
38
|
+
stash_dir, # type: str
|
|
39
|
+
record_relpath, # type: Text
|
|
40
|
+
root_is_purelib, # type: bool
|
|
41
|
+
sys_path_entries, # type: Tuple[str, ...]
|
|
42
|
+
):
|
|
43
|
+
# type: (...) -> InstalledWheel
|
|
44
|
+
|
|
45
|
+
# We currently need the installed wheel chroot hash for PEX-INFO / boot purposes. It is
|
|
46
|
+
# expensive to calculate; so we do it here 1 time when saving the installed wheel.
|
|
47
|
+
fingerprint = CacheHelper.dir_hash(prefix_dir, hasher=hashlib.sha256)
|
|
48
|
+
|
|
49
|
+
layout = {
|
|
50
|
+
"stash_dir": stash_dir,
|
|
51
|
+
"record_relpath": record_relpath,
|
|
52
|
+
"fingerprint": fingerprint,
|
|
53
|
+
"root_is_purelib": root_is_purelib,
|
|
54
|
+
"sys_path_entries": sys_path_entries,
|
|
55
|
+
}
|
|
56
|
+
with open(cls.layout_file(prefix_dir), "w") as fp:
|
|
57
|
+
json.dump(layout, fp, sort_keys=True, separators=(",", ":"))
|
|
58
|
+
return cls(
|
|
59
|
+
prefix_dir=prefix_dir,
|
|
60
|
+
stash_dir=stash_dir,
|
|
61
|
+
record_relpath=record_relpath,
|
|
62
|
+
fingerprint=fingerprint,
|
|
63
|
+
root_is_purelib=root_is_purelib,
|
|
64
|
+
sys_path_entries=sys_path_entries,
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
@classmethod
|
|
68
|
+
def load(cls, prefix_dir):
|
|
69
|
+
# type: (str) -> InstalledWheel
|
|
70
|
+
layout_file = cls.layout_file(prefix_dir)
|
|
71
|
+
try:
|
|
72
|
+
with open(layout_file) as fp:
|
|
73
|
+
layout = json.load(fp)
|
|
74
|
+
except (IOError, OSError) as e:
|
|
75
|
+
raise cls.LoadError(
|
|
76
|
+
"Failed to load an installed wheel layout from {layout_file}: {err}".format(
|
|
77
|
+
layout_file=layout_file, err=e
|
|
78
|
+
)
|
|
79
|
+
)
|
|
80
|
+
if not isinstance(layout, dict):
|
|
81
|
+
raise cls.LoadError(
|
|
82
|
+
"The installed wheel layout file at {layout_file} must contain a single top-level "
|
|
83
|
+
"object, found: {value}.".format(layout_file=layout_file, value=layout)
|
|
84
|
+
)
|
|
85
|
+
stash_dir = layout.get("stash_dir")
|
|
86
|
+
record_relpath = layout.get("record_relpath")
|
|
87
|
+
if not stash_dir or not record_relpath:
|
|
88
|
+
raise cls.LoadError(
|
|
89
|
+
"The installed wheel layout file at {layout_file} must contain an object with both "
|
|
90
|
+
"`stash_dir` and `record_relpath` attributes, found: {value}".format(
|
|
91
|
+
layout_file=layout_file, value=layout
|
|
92
|
+
)
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
fingerprint = layout.get("fingerprint")
|
|
96
|
+
|
|
97
|
+
# N.B.: Caching root_is_purelib was not part of the original InstalledWheel layout data; so
|
|
98
|
+
# we materialize the property if needed to support older installed wheel chroots.
|
|
99
|
+
root_is_purelib = layout.get("root_is_purelib")
|
|
100
|
+
if root_is_purelib is None:
|
|
101
|
+
try:
|
|
102
|
+
wheel = WHEEL.load(prefix_dir)
|
|
103
|
+
except WheelMetadataLoadError as e:
|
|
104
|
+
raise cls.LoadError(
|
|
105
|
+
"Failed to determine if installed wheel at {location} is platform-specific: "
|
|
106
|
+
"{err}".format(location=prefix_dir, err=e)
|
|
107
|
+
)
|
|
108
|
+
root_is_purelib = wheel.root_is_purelib
|
|
109
|
+
|
|
110
|
+
# N.B.: Older versions of Pex installed wheel chroots did not have this field since the
|
|
111
|
+
# `sys.path` entry was always just the prefix_dir for those.
|
|
112
|
+
sys_path_entries = layout.get("sys_path_entries", [""])
|
|
113
|
+
|
|
114
|
+
return cls(
|
|
115
|
+
prefix_dir=prefix_dir,
|
|
116
|
+
stash_dir=cast(str, stash_dir),
|
|
117
|
+
record_relpath=cast(str, record_relpath),
|
|
118
|
+
fingerprint=cast("Optional[str]", fingerprint),
|
|
119
|
+
root_is_purelib=root_is_purelib,
|
|
120
|
+
sys_path_entries=tuple(sys_path_entries),
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
prefix_dir = attr.ib() # type: str
|
|
124
|
+
stash_dir = attr.ib() # type: str
|
|
125
|
+
record_relpath = attr.ib() # type: Text
|
|
126
|
+
fingerprint = attr.ib() # type: Optional[str]
|
|
127
|
+
root_is_purelib = attr.ib() # type: bool
|
|
128
|
+
sys_path_entries = attr.ib() # type: Tuple[str, ...]
|
|
129
|
+
|
|
130
|
+
def wheel_file_name(self):
|
|
131
|
+
# type: () -> str
|
|
132
|
+
return Wheel.load(self.prefix_dir).wheel_file_name
|
|
133
|
+
|
|
134
|
+
def stashed_path(self, *components):
|
|
135
|
+
# type: (*str) -> str
|
|
136
|
+
return os.path.join(self.prefix_dir, self.stash_dir, *components)
|
|
137
|
+
|
|
138
|
+
def iter_sys_path_entries(self):
|
|
139
|
+
# type: () -> Iterator[str]
|
|
140
|
+
for sys_path_entry in self.sys_path_entries:
|
|
141
|
+
yield os.path.normpath(os.path.join(self.prefix_dir, sys_path_entry))
|
pex/interpreter.py
CHANGED
|
@@ -18,7 +18,9 @@ from textwrap import dedent
|
|
|
18
18
|
from pex import third_party
|
|
19
19
|
from pex.cache.dirs import InterpreterDir
|
|
20
20
|
from pex.common import safe_mkdtemp, safe_rmtree
|
|
21
|
+
from pex.exceptions import production_assert
|
|
21
22
|
from pex.executor import Executor
|
|
23
|
+
from pex.interpreter_implementation import InterpreterImplementation
|
|
22
24
|
from pex.jobs import Job, Retain, SpawnedJob, execute_parallel
|
|
23
25
|
from pex.orderedset import OrderedSet
|
|
24
26
|
from pex.os import WINDOWS, is_exe
|
|
@@ -61,18 +63,6 @@ if TYPE_CHECKING:
|
|
|
61
63
|
InterpreterOrError = Union["PythonInterpreter", InterpreterIdentificationError]
|
|
62
64
|
|
|
63
65
|
|
|
64
|
-
def calculate_binary_name(
|
|
65
|
-
platform_python_implementation, python_version=None # type: Optional[Tuple[int, ...]]
|
|
66
|
-
):
|
|
67
|
-
# type: (...) -> str
|
|
68
|
-
name = "python"
|
|
69
|
-
if platform_python_implementation == "PyPy":
|
|
70
|
-
name = "pypy"
|
|
71
|
-
if not python_version:
|
|
72
|
-
return name
|
|
73
|
-
return "{name}{version}".format(name=name, version=".".join(map(str, python_version)))
|
|
74
|
-
|
|
75
|
-
|
|
76
66
|
class SitePackagesDir(object):
|
|
77
67
|
def __init__(self, path):
|
|
78
68
|
# type: (str) -> None
|
|
@@ -404,11 +394,11 @@ class PythonIdentity(object):
|
|
|
404
394
|
)
|
|
405
395
|
|
|
406
396
|
@classmethod
|
|
407
|
-
def
|
|
408
|
-
# type: (str) ->
|
|
409
|
-
for
|
|
410
|
-
if python_tag.startswith(abbr):
|
|
411
|
-
return
|
|
397
|
+
def _find_implementation(cls, python_tag):
|
|
398
|
+
# type: (str) -> InterpreterImplementation.Value
|
|
399
|
+
for implementation in InterpreterImplementation.values():
|
|
400
|
+
if python_tag.startswith(implementation.abbr):
|
|
401
|
+
return implementation
|
|
412
402
|
raise ValueError("Unknown interpreter: {}".format(python_tag))
|
|
413
403
|
|
|
414
404
|
def __init__(
|
|
@@ -431,9 +421,12 @@ class PythonIdentity(object):
|
|
|
431
421
|
configured_macosx_deployment_target, # type: Optional[str]
|
|
432
422
|
):
|
|
433
423
|
# type: (...) -> None
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
424
|
+
|
|
425
|
+
self._implementation = self._find_implementation(python_tag)
|
|
426
|
+
production_assert(
|
|
427
|
+
not pypy_version or self._implementation is InterpreterImplementation.PYPY
|
|
428
|
+
)
|
|
429
|
+
self._pypy_version = pypy_version
|
|
437
430
|
|
|
438
431
|
self._binary = binary
|
|
439
432
|
self._prefix = prefix
|
|
@@ -447,7 +440,6 @@ class PythonIdentity(object):
|
|
|
447
440
|
self._abi_tag = abi_tag
|
|
448
441
|
self._platform_tag = platform_tag
|
|
449
442
|
self._version = version
|
|
450
|
-
self._pypy_version = pypy_version
|
|
451
443
|
self._supported_tags = CompatibilityTags(tags=supported_tags)
|
|
452
444
|
self._env_markers = env_markers
|
|
453
445
|
self._configured_macosx_deployment_target = configured_macosx_deployment_target
|
|
@@ -491,7 +483,7 @@ class PythonIdentity(object):
|
|
|
491
483
|
env_markers=self._env_markers.as_dict(),
|
|
492
484
|
configured_macosx_deployment_target=self._configured_macosx_deployment_target,
|
|
493
485
|
)
|
|
494
|
-
return json.dumps(values, sort_keys=True)
|
|
486
|
+
return json.dumps(values, sort_keys=True, separators=(",", ":"))
|
|
495
487
|
|
|
496
488
|
@property
|
|
497
489
|
def binary(self):
|
|
@@ -570,7 +562,7 @@ class PythonIdentity(object):
|
|
|
570
562
|
@property
|
|
571
563
|
def is_pypy(self):
|
|
572
564
|
# type: () -> bool
|
|
573
|
-
return
|
|
565
|
+
return self._implementation is InterpreterImplementation.PYPY
|
|
574
566
|
|
|
575
567
|
@property
|
|
576
568
|
def version_str(self):
|
|
@@ -593,9 +585,9 @@ class PythonIdentity(object):
|
|
|
593
585
|
return self._configured_macosx_deployment_target
|
|
594
586
|
|
|
595
587
|
@property
|
|
596
|
-
def
|
|
597
|
-
# type: () ->
|
|
598
|
-
return self.
|
|
588
|
+
def implementation(self):
|
|
589
|
+
# type: () -> InterpreterImplementation.Value
|
|
590
|
+
return self._implementation
|
|
599
591
|
|
|
600
592
|
def iter_supported_platforms(self):
|
|
601
593
|
# type: () -> Iterator[Platform]
|
|
@@ -614,9 +606,8 @@ class PythonIdentity(object):
|
|
|
614
606
|
|
|
615
607
|
def binary_name(self, version_components=2):
|
|
616
608
|
# type: (int) -> str
|
|
617
|
-
return calculate_binary_name(
|
|
618
|
-
|
|
619
|
-
python_version=self._version[:version_components] if version_components > 0 else None,
|
|
609
|
+
return self._implementation.calculate_binary_name(
|
|
610
|
+
version=self._version[:version_components] if version_components > 0 else None
|
|
620
611
|
)
|
|
621
612
|
|
|
622
613
|
def hashbang(self):
|
|
@@ -636,8 +627,8 @@ class PythonIdentity(object):
|
|
|
636
627
|
# type: () -> str
|
|
637
628
|
# N.B.: Kept as distinct from __repr__ to support legacy str(identity) used by Pants v1 when
|
|
638
629
|
# forming cache locations.
|
|
639
|
-
return "{
|
|
640
|
-
|
|
630
|
+
return "{implementation}-{major}.{minor}.{patch}".format(
|
|
631
|
+
implementation=self._implementation,
|
|
641
632
|
major=self._version[0],
|
|
642
633
|
minor=self._version[1],
|
|
643
634
|
patch=self._version[2],
|
|
@@ -1438,13 +1429,13 @@ class PythonInterpreter(object):
|
|
|
1438
1429
|
# python<major>.<minor> is present in any given <prefix>/bin/ directory; so the algorithm
|
|
1439
1430
|
# gets a hit on 1st try for CPython binaries incurring ~no extra overhead.
|
|
1440
1431
|
|
|
1432
|
+
implementation = self._identity.implementation
|
|
1441
1433
|
version = self._identity.version
|
|
1442
1434
|
abi_tag = self._identity.abi_tag
|
|
1443
1435
|
|
|
1444
|
-
|
|
1445
|
-
suffixes = ("{}.{}".format(version[0], version[1]), str(version[0]), "")
|
|
1436
|
+
versions = version[:2], version[:1], None
|
|
1446
1437
|
candidate_binaries = tuple(
|
|
1447
|
-
script_name(
|
|
1438
|
+
script_name(implementation.calculate_binary_name(version)) for version in versions
|
|
1448
1439
|
)
|
|
1449
1440
|
|
|
1450
1441
|
def iter_base_candidate_binary_paths(interpreter):
|
|
@@ -1534,13 +1525,19 @@ class PythonInterpreter(object):
|
|
|
1534
1525
|
self._supported_platforms = frozenset(self._identity.iter_supported_platforms())
|
|
1535
1526
|
return self._supported_platforms
|
|
1536
1527
|
|
|
1537
|
-
def shebang(
|
|
1538
|
-
|
|
1528
|
+
def shebang(
|
|
1529
|
+
self,
|
|
1530
|
+
args=None, # type: Optional[Text]
|
|
1531
|
+
encoding_line="", # type: str
|
|
1532
|
+
):
|
|
1533
|
+
# type: (...) -> Text
|
|
1539
1534
|
"""Return the contents of an appropriate shebang for this interpreter and args.
|
|
1540
1535
|
|
|
1541
1536
|
The shebang will include the leading `#!` but will not include a trailing new line character.
|
|
1542
1537
|
"""
|
|
1543
|
-
return create_shebang(
|
|
1538
|
+
return create_shebang(
|
|
1539
|
+
adjust_to_final_path(self._binary), python_args=args, encoding_line=encoding_line
|
|
1540
|
+
)
|
|
1544
1541
|
|
|
1545
1542
|
def create_isolated_cmd(
|
|
1546
1543
|
self,
|
|
@@ -1646,6 +1643,7 @@ def create_shebang(
|
|
|
1646
1643
|
python_exe, # type: Text
|
|
1647
1644
|
python_args=None, # type: Optional[Text]
|
|
1648
1645
|
max_shebang_length=MAX_SHEBANG_LENGTH, # type: int
|
|
1646
|
+
encoding_line="", # type: str
|
|
1649
1647
|
):
|
|
1650
1648
|
# type: (...) -> Text
|
|
1651
1649
|
"""Return the contents of an appropriate shebang for the given Python interpreter and args.
|
|
@@ -1671,6 +1669,7 @@ def create_shebang(
|
|
|
1671
1669
|
dedent(
|
|
1672
1670
|
"""\
|
|
1673
1671
|
#!/bin/sh
|
|
1672
|
+
{encoding_line}
|
|
1674
1673
|
# N.B.: This python script executes via a /bin/sh re-exec as a hack to work around a
|
|
1675
1674
|
# potential maximum shebang length of {max_shebang_length} bytes on this system which
|
|
1676
1675
|
# the python interpreter `exec`ed below would violate.
|
|
@@ -1678,6 +1677,10 @@ def create_shebang(
|
|
|
1678
1677
|
'''
|
|
1679
1678
|
"""
|
|
1680
1679
|
)
|
|
1681
|
-
.format(
|
|
1680
|
+
.format(
|
|
1681
|
+
encoding_line=encoding_line.rstrip(),
|
|
1682
|
+
max_shebang_length=max_shebang_length,
|
|
1683
|
+
python=python,
|
|
1684
|
+
)
|
|
1682
1685
|
.strip()
|
|
1683
1686
|
)
|