pex 2.59.4__py2.py3-none-any.whl → 2.60.0__py2.py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of pex might be problematic. Click here for more details.

Files changed (114) hide show
  1. pex/build_backend/wrap.py +25 -4
  2. pex/cache/dirs.py +14 -4
  3. pex/cli/commands/lock.py +8 -5
  4. pex/common.py +57 -7
  5. pex/compatibility.py +1 -1
  6. pex/dist_metadata.py +48 -6
  7. pex/docs/html/_pagefind/fragment/en_39c0488.pf_fragment +0 -0
  8. pex/docs/html/_pagefind/fragment/en_3eeaaf4.pf_fragment +0 -0
  9. pex/docs/html/_pagefind/fragment/en_a1dde36.pf_fragment +0 -0
  10. pex/docs/html/_pagefind/fragment/en_a755644.pf_fragment +0 -0
  11. pex/docs/html/_pagefind/fragment/en_b16e3bd.pf_fragment +0 -0
  12. pex/docs/html/_pagefind/fragment/{en_e323b0a.pf_fragment → en_c5d35a7.pf_fragment} +0 -0
  13. pex/docs/html/_pagefind/fragment/en_ec62bd2.pf_fragment +0 -0
  14. pex/docs/html/_pagefind/fragment/en_f32628f.pf_fragment +0 -0
  15. pex/docs/html/_pagefind/index/{en_9894162.pf_index → en_b211695.pf_index} +0 -0
  16. pex/docs/html/_pagefind/pagefind-entry.json +1 -1
  17. pex/docs/html/_pagefind/pagefind.en_e8a49380e5.pf_meta +0 -0
  18. pex/docs/html/_static/documentation_options.js +1 -1
  19. pex/docs/html/api/vars.html +5 -5
  20. pex/docs/html/buildingpex.html +5 -5
  21. pex/docs/html/genindex.html +5 -5
  22. pex/docs/html/index.html +5 -5
  23. pex/docs/html/recipes.html +5 -5
  24. pex/docs/html/scie.html +5 -5
  25. pex/docs/html/search.html +5 -5
  26. pex/docs/html/whatispex.html +5 -5
  27. pex/entry_points_txt.py +98 -0
  28. pex/environment.py +13 -10
  29. pex/finders.py +1 -1
  30. pex/installed_wheel.py +127 -0
  31. pex/interpreter.py +17 -5
  32. pex/interpreter_constraints.py +4 -4
  33. pex/pep_376.py +37 -385
  34. pex/pep_427.py +736 -248
  35. pex/pex_builder.py +4 -4
  36. pex/pex_info.py +8 -3
  37. pex/resolve/venv_resolver.py +98 -23
  38. pex/resolver.py +10 -3
  39. pex/sysconfig.py +5 -3
  40. pex/third_party/__init__.py +1 -1
  41. pex/tools/commands/repository.py +47 -24
  42. pex/vendor/__init__.py +4 -9
  43. pex/vendor/__main__.py +62 -41
  44. pex/vendor/_vendored/ansicolors/.layout.json +1 -1
  45. pex/vendor/_vendored/ansicolors/ansicolors-1.1.8.dist-info/RECORD +11 -0
  46. pex/vendor/_vendored/ansicolors/ansicolors-1.1.8.pex-info/original-whl-info.json +1 -0
  47. pex/vendor/_vendored/appdirs/.layout.json +1 -1
  48. pex/vendor/_vendored/appdirs/appdirs-1.4.4.dist-info/RECORD +7 -0
  49. pex/vendor/_vendored/appdirs/appdirs-1.4.4.pex-info/original-whl-info.json +1 -0
  50. pex/vendor/_vendored/attrs/.layout.json +1 -1
  51. pex/vendor/_vendored/attrs/attrs-21.5.0.dev0.dist-info/RECORD +37 -0
  52. pex/vendor/_vendored/attrs/attrs-21.5.0.dev0.pex-info/original-whl-info.json +1 -0
  53. pex/vendor/_vendored/packaging_20_9/.layout.json +1 -1
  54. pex/vendor/_vendored/packaging_20_9/packaging-20.9.dist-info/RECORD +20 -0
  55. pex/vendor/_vendored/packaging_20_9/packaging-20.9.pex-info/original-whl-info.json +1 -0
  56. pex/vendor/_vendored/packaging_20_9/pyparsing-2.4.7.dist-info/RECORD +7 -0
  57. pex/vendor/_vendored/packaging_20_9/pyparsing-2.4.7.pex-info/original-whl-info.json +1 -0
  58. pex/vendor/_vendored/packaging_21_3/.layout.json +1 -1
  59. pex/vendor/_vendored/packaging_21_3/packaging-21.3.dist-info/RECORD +20 -0
  60. pex/vendor/_vendored/packaging_21_3/packaging-21.3.pex-info/original-whl-info.json +1 -0
  61. pex/vendor/_vendored/packaging_21_3/pyparsing-3.0.7.dist-info/RECORD +18 -0
  62. pex/vendor/_vendored/packaging_21_3/pyparsing-3.0.7.pex-info/original-whl-info.json +1 -0
  63. pex/vendor/_vendored/packaging_24_0/.layout.json +1 -1
  64. pex/vendor/_vendored/packaging_24_0/packaging-24.0.dist-info/RECORD +22 -0
  65. pex/vendor/_vendored/packaging_24_0/packaging-24.0.pex-info/original-whl-info.json +1 -0
  66. pex/vendor/_vendored/packaging_25_0/.layout.json +1 -1
  67. pex/vendor/_vendored/packaging_25_0/packaging-25.0.dist-info/RECORD +24 -0
  68. pex/vendor/_vendored/packaging_25_0/packaging-25.0.pex-info/original-whl-info.json +1 -0
  69. pex/vendor/_vendored/pip/.layout.json +1 -1
  70. pex/vendor/_vendored/pip/pip/_vendor/certifi/cacert.pem +63 -1
  71. pex/vendor/_vendored/pip/pip-20.3.4.dist-info/RECORD +388 -0
  72. pex/vendor/_vendored/pip/pip-20.3.4.pex-info/original-whl-info.json +1 -0
  73. pex/vendor/_vendored/setuptools/.layout.json +1 -1
  74. pex/vendor/_vendored/setuptools/setuptools-44.0.0+3acb925dd708430aeaf197ea53ac8a752f7c1863.dist-info/RECORD +107 -0
  75. pex/vendor/_vendored/setuptools/setuptools-44.0.0+3acb925dd708430aeaf197ea53ac8a752f7c1863.pex-info/original-whl-info.json +1 -0
  76. pex/vendor/_vendored/toml/.layout.json +1 -1
  77. pex/vendor/_vendored/toml/toml-0.10.2.dist-info/RECORD +11 -0
  78. pex/vendor/_vendored/toml/toml-0.10.2.pex-info/original-whl-info.json +1 -0
  79. pex/vendor/_vendored/tomli/.layout.json +1 -1
  80. pex/vendor/_vendored/tomli/tomli-2.0.1.dist-info/RECORD +10 -0
  81. pex/vendor/_vendored/tomli/tomli-2.0.1.pex-info/original-whl-info.json +1 -0
  82. pex/venv/installer.py +9 -5
  83. pex/version.py +1 -1
  84. pex/wheel.py +79 -15
  85. pex/whl.py +67 -0
  86. pex/windows/__init__.py +14 -11
  87. {pex-2.59.4.dist-info → pex-2.60.0.dist-info}/METADATA +4 -4
  88. {pex-2.59.4.dist-info → pex-2.60.0.dist-info}/RECORD +93 -77
  89. pex/docs/html/_pagefind/fragment/en_144b803.pf_fragment +0 -0
  90. pex/docs/html/_pagefind/fragment/en_1df1379.pf_fragment +0 -0
  91. pex/docs/html/_pagefind/fragment/en_2c6c6cb.pf_fragment +0 -0
  92. pex/docs/html/_pagefind/fragment/en_a916b1c.pf_fragment +0 -0
  93. pex/docs/html/_pagefind/fragment/en_b33e5d4.pf_fragment +0 -0
  94. pex/docs/html/_pagefind/fragment/en_c1c571a.pf_fragment +0 -0
  95. pex/docs/html/_pagefind/fragment/en_fda06e7.pf_fragment +0 -0
  96. pex/docs/html/_pagefind/pagefind.en_84c8322e7a.pf_meta +0 -0
  97. pex/vendor/_vendored/ansicolors/ansicolors-1.1.8.dist-info/INSTALLER +0 -1
  98. pex/vendor/_vendored/appdirs/appdirs-1.4.4.dist-info/INSTALLER +0 -1
  99. pex/vendor/_vendored/attrs/attrs-21.5.0.dev0.dist-info/INSTALLER +0 -1
  100. pex/vendor/_vendored/packaging_20_9/packaging-20.9.dist-info/INSTALLER +0 -1
  101. pex/vendor/_vendored/packaging_20_9/pyparsing-2.4.7.dist-info/INSTALLER +0 -1
  102. pex/vendor/_vendored/packaging_21_3/packaging-21.3.dist-info/INSTALLER +0 -1
  103. pex/vendor/_vendored/packaging_21_3/pyparsing-3.0.7.dist-info/INSTALLER +0 -1
  104. pex/vendor/_vendored/packaging_24_0/packaging-24.0.dist-info/INSTALLER +0 -1
  105. pex/vendor/_vendored/packaging_25_0/packaging-25.0.dist-info/INSTALLER +0 -1
  106. pex/vendor/_vendored/pip/pip-20.3.4.dist-info/INSTALLER +0 -1
  107. pex/vendor/_vendored/setuptools/setuptools-44.0.0+3acb925dd708430aeaf197ea53ac8a752f7c1863.dist-info/INSTALLER +0 -1
  108. pex/vendor/_vendored/toml/toml-0.10.2.dist-info/INSTALLER +0 -1
  109. pex/vendor/_vendored/tomli/tomli-2.0.1.dist-info/INSTALLER +0 -1
  110. {pex-2.59.4.dist-info → pex-2.60.0.dist-info}/WHEEL +0 -0
  111. {pex-2.59.4.dist-info → pex-2.60.0.dist-info}/entry_points.txt +0 -0
  112. {pex-2.59.4.dist-info → pex-2.60.0.dist-info}/licenses/LICENSE +0 -0
  113. {pex-2.59.4.dist-info → pex-2.60.0.dist-info}/pylock/pylock.toml +0 -0
  114. {pex-2.59.4.dist-info → pex-2.60.0.dist-info}/top_level.txt +0 -0
pex/pex_builder.py CHANGED
@@ -33,11 +33,11 @@ from pex.enum import Enum
33
33
  from pex.executables import chmod_plus_x, create_sh_python_redirector_shebang
34
34
  from pex.finders import get_entry_point_from_console_script, get_script_from_distributions
35
35
  from pex.fs import safe_rename, safe_symlink
36
+ from pex.installed_wheel import InstalledWheel
36
37
  from pex.interpreter import PythonInterpreter
37
38
  from pex.layout import Layout
38
39
  from pex.orderedset import OrderedSet
39
40
  from pex.os import WINDOWS
40
- from pex.pep_376 import InstalledWheel
41
41
  from pex.pex import PEX
42
42
  from pex.pex_info import PexInfo
43
43
  from pex.sh_boot import create_sh_boot_script
@@ -790,9 +790,9 @@ class PEXBuilder(object):
790
790
  self._chroot.zip(
791
791
  os.path.join(atomic_zip_dir.work_dir, location),
792
792
  deterministic=deterministic,
793
- exclude_file=is_pyc_temporary_file
794
- if bytecode_compile
795
- else is_pyc_file,
793
+ exclude_file=(
794
+ is_pyc_temporary_file if bytecode_compile else is_pyc_file
795
+ ),
796
796
  strip_prefix=os.path.join(pex_info.internal_cache, location),
797
797
  labels=(location,),
798
798
  compress=compress,
pex/pex_info.py CHANGED
@@ -604,9 +604,14 @@ class PexInfo(object):
604
604
  data["distributions"] = self._distributions.copy()
605
605
  return data
606
606
 
607
- def dump(self, **extra_json_dumps_kwargs):
608
- # type: (**Any) -> str
609
- return json.dumps(self.as_json_dict(), sort_keys=True, **extra_json_dumps_kwargs)
607
+ def dump(self, indent=None):
608
+ # type: (Optional[int]) -> str
609
+ return json.dumps(
610
+ self.as_json_dict(),
611
+ sort_keys=True,
612
+ indent=indent,
613
+ separators=None if indent else (",", ":"),
614
+ )
610
615
 
611
616
  def copy(self):
612
617
  # type: () -> PexInfo
@@ -5,6 +5,7 @@ from __future__ import absolute_import
5
5
 
6
6
  import functools
7
7
  import hashlib
8
+ import io
8
9
  import itertools
9
10
  import os
10
11
  from collections import defaultdict, deque
@@ -13,6 +14,7 @@ from pex import pex_warnings
13
14
  from pex.atomic_directory import atomic_directory
14
15
  from pex.cache.dirs import CacheDir, InstalledWheelDir
15
16
  from pex.common import safe_relative_symlink
17
+ from pex.compatibility import PY2, commonpath
16
18
  from pex.dependency_configuration import DependencyConfiguration
17
19
  from pex.dist_metadata import (
18
20
  Constraint,
@@ -24,9 +26,11 @@ from pex.dist_metadata import (
24
26
  )
25
27
  from pex.exceptions import production_assert, reportable_unexpected_error_msg
26
28
  from pex.fingerprinted_distribution import FingerprintedDistribution
29
+ from pex.installed_wheel import InstalledWheel
30
+ from pex.interpreter import PythonInterpreter
27
31
  from pex.jobs import DEFAULT_MAX_JOBS, iter_map_parallel
28
32
  from pex.orderedset import OrderedSet
29
- from pex.pep_376 import InstalledWheel
33
+ from pex.pep_376 import Record
30
34
  from pex.pep_427 import InstallableType, InstallableWheel, InstallPaths, install_wheel_chroot
31
35
  from pex.pep_503 import ProjectName
32
36
  from pex.pip.version import PipVersion
@@ -36,10 +40,12 @@ from pex.resolve.requirement_configuration import RequirementConfiguration
36
40
  from pex.resolve.resolver_configuration import PipConfiguration
37
41
  from pex.resolve.resolvers import ResolvedDistribution, Resolver, ResolveResult
38
42
  from pex.result import Error
43
+ from pex.sysconfig import script_name
39
44
  from pex.targets import LocalInterpreter, Target, Targets
40
45
  from pex.typing import TYPE_CHECKING
41
46
  from pex.venv.virtualenv import Virtualenv
42
47
  from pex.wheel import Wheel
48
+ from pex.whl import repacked_whl
43
49
 
44
50
  if TYPE_CHECKING:
45
51
  from typing import DefaultDict, Deque, FrozenSet, Iterable, Iterator, List, Mapping, Set, Union
@@ -49,17 +55,71 @@ else:
49
55
  import pex.third_party.attr as attr
50
56
 
51
57
 
58
+ def _normalize_record(
59
+ distribution, # type: Distribution
60
+ install_paths, # type: InstallPaths
61
+ record_data, # type: bytes
62
+ ):
63
+ # type: (...) -> bytes
64
+
65
+ entry_map = distribution.get_entry_map()
66
+ entry_point_scripts = {
67
+ script_name(entry_point)
68
+ for key in ("console_scripts", "gui_scripts")
69
+ for entry_point in entry_map.get(key, {})
70
+ }
71
+ if not entry_point_scripts:
72
+ return record_data
73
+
74
+ scripts_dir = os.path.realpath(install_paths.scripts)
75
+ record_lines = record_data.decode("utf-8").splitlines(True) # N.B. no kw in 2.7: keepends=True
76
+ eol = os.sep
77
+ if record_lines:
78
+ eol = "\r\n" if record_lines[0].endswith("\r\n") else "\n"
79
+
80
+ installed_files = [
81
+ installed_file
82
+ for installed_file in Record.read(lines=iter(record_lines))
83
+ if (
84
+ (os.path.basename(installed_file.path) not in entry_point_scripts)
85
+ or (
86
+ scripts_dir
87
+ != commonpath(
88
+ (
89
+ scripts_dir,
90
+ os.path.realpath(os.path.join(distribution.location, installed_file.path)),
91
+ )
92
+ )
93
+ )
94
+ )
95
+ ]
96
+
97
+ if PY2:
98
+ record_fp = io.BytesIO()
99
+ Record.write_fp(fp=record_fp, installed_files=installed_files, eol=eol)
100
+ return record_fp.getvalue()
101
+ else:
102
+ record_fp = io.StringIO()
103
+ Record.write_fp(fp=record_fp, installed_files=installed_files, eol=eol)
104
+ return record_fp.getvalue().encode("utf-8")
105
+
106
+
52
107
  def _install_distribution(
53
- venv_install_paths, # type: InstallPaths
108
+ interpreter, # type: PythonInterpreter
109
+ result_type, # type: InstallableType.Value
110
+ use_system_time, # type: bool
54
111
  distribution, # type: Distribution
55
112
  ):
56
- # type: (...) -> InstalledWheel
113
+ # type: (...) -> FingerprintedDistribution
57
114
 
58
115
  production_assert(distribution.type is DistributionType.INSTALLED)
59
116
  production_assert(distribution.metadata.files.metadata.type is MetadataType.DIST_INFO)
60
117
 
61
- wheel = InstallableWheel(
62
- wheel=Wheel.from_distribution(distribution), install_paths=venv_install_paths
118
+ venv_install_paths = InstallPaths.interpreter(
119
+ interpreter, project_name=distribution.metadata.project_name
120
+ )
121
+ wheel = InstallableWheel.from_whl(
122
+ whl=Wheel.from_distribution(distribution), install_paths=venv_install_paths
63
123
  )
64
124
  record_data = wheel.metadata_files.read("RECORD")
65
125
  if not record_data:
@@ -74,7 +134,14 @@ def _install_distribution(
74
134
  )
75
135
 
76
136
  installed_wheel_dir = InstalledWheelDir.create(
77
- wheel_name=wheel.wheel_file_name, install_hash=hashlib.sha256(record_data).hexdigest()
137
+ wheel_name=wheel.wheel_file_name,
138
+ install_hash=hashlib.sha256(
139
+ _normalize_record(
140
+ distribution=Distribution(location=wheel.location, metadata=wheel.dist_metadata()),
141
+ install_paths=venv_install_paths,
142
+ record_data=record_data,
143
+ )
144
+ ).hexdigest(),
78
145
  )
79
146
  with atomic_directory(target_dir=installed_wheel_dir) as atomic_dir:
80
147
  if not atomic_dir.is_finalized():
@@ -94,28 +161,37 @@ def _install_distribution(
94
161
  installed_wheel_dir,
95
162
  os.path.join(symlink_atomic_dir.work_dir, wheel.wheel_file_name),
96
163
  )
97
- return InstalledWheel.load(installed_wheel_dir)
164
+
165
+ installed_wheel = InstalledWheel.load(installed_wheel_dir)
166
+ if not installed_wheel.fingerprint:
167
+ raise AssertionError(reportable_unexpected_error_msg())
168
+
169
+ if result_type is InstallableType.INSTALLED_WHEEL_CHROOT:
170
+ return FingerprintedDistribution(
171
+ distribution=Distribution.load(installed_wheel.prefix_dir),
172
+ fingerprint=installed_wheel.fingerprint,
173
+ )
174
+ return repacked_whl(
175
+ installed_wheel, fingerprint=installed_wheel.fingerprint, use_system_time=use_system_time
176
+ )
98
177
 
99
178
 
100
179
  def _install_venv_distributions(
101
180
  venv, # type: Virtualenv
102
181
  distributions, # type: Iterable[Distribution]
103
182
  max_install_jobs=DEFAULT_MAX_JOBS, # type: int
183
+ result_type=InstallableType.INSTALLED_WHEEL_CHROOT, # type: InstallableType.Value
184
+ use_system_time=False, # type: bool
104
185
  ):
105
186
  # type: (...) -> Iterator[FingerprintedDistribution]
106
187
 
107
- venv_install_paths = InstallPaths.interpreter(venv.interpreter)
108
- for installed_wheel in iter_map_parallel(
188
+ return iter_map_parallel(
109
189
  inputs=distributions,
110
- function=functools.partial(_install_distribution, venv_install_paths),
190
+ function=functools.partial(
191
+ _install_distribution, venv.interpreter, result_type, use_system_time
192
+ ),
111
193
  max_jobs=max_install_jobs,
112
- ):
113
- if not installed_wheel.fingerprint:
114
- raise AssertionError(reportable_unexpected_error_msg())
115
- yield FingerprintedDistribution(
116
- distribution=Distribution.load(installed_wheel.prefix_dir),
117
- fingerprint=installed_wheel.fingerprint,
118
- )
194
+ )
119
195
 
120
196
 
121
197
  @attr.s(frozen=True)
@@ -186,6 +262,7 @@ def _resolve_distributions(
186
262
  allow_prereleases=False, # type: bool
187
263
  compile=False, # type: bool
188
264
  ignore_errors=False, # type: bool
265
+ result_type=InstallableType.INSTALLED_WHEEL_CHROOT, # type: InstallableType.Value
189
266
  ):
190
267
  # type: (...) -> Iterator[Union[Distribution, FingerprintedDistribution, Error]]
191
268
 
@@ -261,6 +338,7 @@ def _resolve_distributions(
261
338
  transitive=False,
262
339
  compile=compile,
263
340
  ignore_errors=ignore_errors,
341
+ result_type=result_type,
264
342
  )
265
343
  for dist in result.distributions:
266
344
  to_resolve.extend(
@@ -300,12 +378,6 @@ def resolve_from_venv(
300
378
  ):
301
379
  # type: (...) -> Union[ResolveResult, Error]
302
380
 
303
- if result_type is InstallableType.WHEEL_FILE:
304
- return Error(
305
- "Cannot resolve .whl files from virtual environment at {venv_dir}; its distributions "
306
- "are all installed.".format(venv_dir=venv.venv_dir)
307
- )
308
-
309
381
  target = LocalInterpreter.create(venv.interpreter)
310
382
  if not targets.is_empty:
311
383
  return Error(
@@ -392,6 +464,7 @@ def resolve_from_venv(
392
464
  allow_prereleases=pip_configuration.allow_prereleases,
393
465
  compile=compile,
394
466
  ignore_errors=ignore_errors,
467
+ result_type=result_type,
395
468
  ):
396
469
  if isinstance(distribution_or_error, Error):
397
470
  return distribution_or_error
@@ -437,6 +510,8 @@ def resolve_from_venv(
437
510
  venv=venv,
438
511
  distributions=venv_distributions,
439
512
  max_install_jobs=pip_configuration.max_jobs,
513
+ result_type=result_type,
514
+ use_system_time=True,
440
515
  )
441
516
  ),
442
517
  fingerprinted_distributions,
pex/resolver.py CHANGED
@@ -37,10 +37,10 @@ from pex.dist_metadata import (
37
37
  )
38
38
  from pex.exceptions import production_assert
39
39
  from pex.fingerprinted_distribution import FingerprintedDistribution
40
+ from pex.installed_wheel import InstalledWheel
40
41
  from pex.jobs import Raise, SpawnedJob, execute_parallel, iter_map_parallel
41
42
  from pex.network_configuration import NetworkConfiguration
42
43
  from pex.orderedset import OrderedSet
43
- from pex.pep_376 import InstalledWheel
44
44
  from pex.pep_425 import CompatibilityTags
45
45
  from pex.pep_427 import InstallableType, WheelError, install_wheel_chroot
46
46
  from pex.pep_503 import ProjectName
@@ -622,7 +622,7 @@ class BuildResult(object):
622
622
  target=self.request.target.render_description(),
623
623
  )
624
624
  )
625
- return InstallRequest.create(self.request.target, wheel_path)
625
+ return InstallRequest.create(self.request.target, wheel_path, was_built_locally=True)
626
626
 
627
627
 
628
628
  @attr.s(frozen=True)
@@ -632,6 +632,7 @@ class InstallRequest(object):
632
632
  cls,
633
633
  target, # type: Union[DownloadTarget, Target]
634
634
  wheel_path, # type: str
635
+ was_built_locally=False, # type: bool
635
636
  ):
636
637
  # type: (...) -> InstallRequest
637
638
  fingerprint = fingerprint_path(wheel_path)
@@ -639,11 +640,13 @@ class InstallRequest(object):
639
640
  download_target=_as_download_target(target),
640
641
  wheel_path=wheel_path,
641
642
  fingerprint=fingerprint,
643
+ was_built_locally=was_built_locally,
642
644
  )
643
645
 
644
646
  download_target = attr.ib(converter=_as_download_target) # type: DownloadTarget
645
647
  wheel_path = attr.ib() # type: str
646
648
  fingerprint = attr.ib() # type: str
649
+ was_built_locally = attr.ib(default=False) # type: bool
647
650
 
648
651
  @property
649
652
  def target(self):
@@ -987,7 +990,11 @@ def _perform_install(
987
990
  ):
988
991
  # type: (...) -> InstallResult
989
992
  install_result = install_request.result(installed_wheels_dir)
990
- install_wheel_chroot(wheel=install_request.wheel_path, destination=install_result.build_chroot)
993
+ install_wheel_chroot(
994
+ wheel=install_request.wheel_path,
995
+ destination=install_result.build_chroot,
996
+ normalize_file_stat=install_request.was_built_locally,
997
+ )
991
998
  return install_result
992
999
 
993
1000
 
pex/sysconfig.py CHANGED
@@ -82,7 +82,7 @@ class _PlatformValue(Enum.Value):
82
82
  self.arch = arch
83
83
 
84
84
  @property
85
- def extension(self):
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(binary_name=binary_name, extension=self.extension)
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.extension
103
+ binary_name=binary_name, platform=self, extension=self.exe_extension
102
104
  )
103
105
 
104
106
  def qualified_file_name(self, file_name):
@@ -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.pep_376 import InstalledWheel
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.
@@ -18,12 +18,14 @@ from pex import dist_metadata
18
18
  from pex.atomic_directory import atomic_directory
19
19
  from pex.cache.dirs import CacheDir
20
20
  from pex.commands.command import JsonMixin, OutputMixin
21
- from pex.common import REPRODUCIBLE_BUILDS_ENV, pluralize, safe_mkdir, safe_mkdtemp, safe_open
21
+ from pex.common import pluralize, safe_mkdir, safe_mkdtemp, safe_open
22
22
  from pex.compatibility import Queue
23
23
  from pex.dist_metadata import Distribution
24
24
  from pex.environment import PEXEnvironment
25
+ from pex.installed_wheel import InstalledWheel
25
26
  from pex.interpreter import PythonInterpreter
26
- from pex.jobs import Job, Retain, SpawnedJob, execute_parallel
27
+ from pex.jobs import Job, iter_map_parallel
28
+ from pex.pep_427 import WheelInstallError, repack
27
29
  from pex.pex import PEX
28
30
  from pex.result import Error, Ok, Result
29
31
  from pex.tools.command import PEXCommand
@@ -31,7 +33,7 @@ from pex.typing import TYPE_CHECKING, cast
31
33
  from pex.venv.virtualenv import InstallationChoice, Virtualenv
32
34
 
33
35
  if TYPE_CHECKING:
34
- from typing import IO, Any, Callable, Iterable, Iterator, List, Text, Tuple
36
+ from typing import IO, Any, Callable, Iterable, Iterator, List, Tuple
35
37
 
36
38
  import attr # vendor:skip
37
39
 
@@ -121,6 +123,29 @@ class FindLinksRepo(object):
121
123
  self._server_process.kill()
122
124
 
123
125
 
126
+ def _extract_wheel(
127
+ distribution, # type: Distribution
128
+ dest_dir, # type: str
129
+ use_system_time=False, # type: bool
130
+ ):
131
+ # type: (...) -> Tuple[Distribution, Result]
132
+
133
+ try:
134
+ whl = repack(
135
+ installed_wheel=InstalledWheel.load(distribution.location),
136
+ dest_dir=dest_dir,
137
+ use_system_time=use_system_time,
138
+ )
139
+ except (InstalledWheel.LoadError, WheelInstallError) as e:
140
+ result = Error(str(e)) # type: Result
141
+ else:
142
+ result = Ok(
143
+ "{distribution}: Repacked wheel as {whl}".format(distribution=distribution, whl=whl)
144
+ )
145
+
146
+ return distribution, result
147
+
148
+
124
149
  class Repository(JsonMixin, OutputMixin, PEXCommand):
125
150
  """Interact with the Python distribution repository contained in a PEX file."""
126
151
 
@@ -270,36 +295,23 @@ class Repository(JsonMixin, OutputMixin, PEXCommand):
270
295
  if self.options.sources:
271
296
  self._extract_sdist(pex, dest_dir)
272
297
 
273
- def spawn_extract(distribution):
274
- # type: (Distribution) -> SpawnedJob[Text]
275
- env = os.environ.copy()
276
- if not self.options.use_system_time:
277
- env.update(REPRODUCIBLE_BUILDS_ENV)
278
- job = spawn_python_job_with_setuptools_and_wheel(
279
- args=["-m", "wheel", "pack", "--dest-dir", dest_dir, distribution.location],
280
- interpreter=pex.interpreter,
281
- stdout=subprocess.PIPE,
282
- env=env,
283
- )
284
- return SpawnedJob.stdout(
285
- job, result_func=lambda out: "{}: {}".format(distribution, out.decode())
286
- )
287
-
288
298
  with self._distributions_output(pex) as (distributions, output):
289
299
  errors = [] # type: List[Distribution]
290
- for result in execute_parallel(
291
- distributions, spawn_extract, error_handler=Retain[Distribution]()
300
+ for distribution, result in iter_map_parallel(
301
+ distributions,
302
+ functools.partial(
303
+ _extract_wheel, dest_dir=dest_dir, use_system_time=self.options.use_system_time
304
+ ),
292
305
  ):
293
- if isinstance(result, tuple):
294
- distribution, error = result
306
+ if isinstance(result, Error):
295
307
  errors.append(distribution)
296
308
  output.write(
297
309
  "Failed to build a wheel for {distribution}: {error}\n".format(
298
- distribution=distribution, error=error
310
+ distribution=distribution, error=result
299
311
  )
300
312
  )
301
313
  else:
302
- output.write(result)
314
+ output.write(str(result))
303
315
  if errors:
304
316
  return Error(
305
317
  "Failed to build wheels for {count} {distributions}.".format(
@@ -435,6 +447,17 @@ class Repository(JsonMixin, OutputMixin, PEXCommand):
435
447
  with open(os.path.join(chroot, "setup.py"), "w") as fp:
436
448
  fp.write("import setuptools; setuptools.setup()")
437
449
 
450
+ with open(os.path.join(chroot, "pyproject.toml"), "w") as fp:
451
+ fp.write(
452
+ dedent(
453
+ """\
454
+ [build-system]
455
+ requires = ["setuptools"]
456
+ backend = "setuptools.build_meta"
457
+ """
458
+ )
459
+ )
460
+
438
461
  spawn_python_job_with_setuptools_and_wheel(
439
462
  args=["setup.py", "sdist", "--dist-dir", dest_dir],
440
463
  interpreter=pex.interpreter,
pex/vendor/__init__.py CHANGED
@@ -201,9 +201,11 @@ class VendorSpec(
201
201
  # Automated update of Pip's vendored certifi's cacert.pem to that from certifi 2025.7.14.
202
202
  # 12.) https://github.com/pex-tool/pip/commit/21a4058f77f9864151510b725419206a2e5645ae
203
203
  # Automated update of Pip's vendored certifi's cacert.pem to that from certifi 2025.8.3.
204
+ # 13.) https://github.com/pex-tool/pip/commit/cef23bb7153ec49037d963e84fcad0f08880fbc5
205
+ # Automated update of Pip's vendored certifi's cacert.pem to that from certifi 2025.10.5.
204
206
  PIP_SPEC = VendorSpec.git(
205
207
  repo="https://github.com/pex-tool/pip",
206
- commit="21a4058f77f9864151510b725419206a2e5645ae",
208
+ commit="cef23bb7153ec49037d963e84fcad0f08880fbc5",
207
209
  project_name="pip",
208
210
  rewrite=False,
209
211
  )
@@ -316,7 +318,6 @@ def vendor_runtime(
316
318
  dest_basedir, # type: str
317
319
  label, # type: str
318
320
  root_module_names, # type: Iterable[str]
319
- include_dist_info=(), # type: Iterable[str]
320
321
  ):
321
322
  # type: (...) -> Set[str]
322
323
  """Includes portions of vendored distributions in a chroot.
@@ -329,7 +330,6 @@ def vendor_runtime(
329
330
  :param dest_basedir: The prefix to store the vendored code under in the ``chroot``.
330
331
  :param label: The chroot label for the vendored code fileset.
331
332
  :param root_module_names: The names of the root vendored modules to include in the chroot.
332
- :param include_dist_info: Include the .dist-info dirs associated with these root module names.
333
333
  :returns: The set of absolute paths of the source files that were vendored.
334
334
  :raise: :class:`ValueError` if any of the given ``root_module_names`` could not be found amongst
335
335
  the vendored code and added to the chroot.
@@ -368,12 +368,7 @@ def vendor_runtime(
368
368
  vendor_module_names[name] = True
369
369
  TRACER.log("Vendoring {} from {} @ {}".format(name, spec, spec.target_dir), V=3)
370
370
 
371
- dirs[:] = packages + [
372
- d
373
- for project in include_dist_info
374
- for d in dirs
375
- if d.startswith(project) and d.endswith(".dist-info")
376
- ]
371
+ dirs[:] = packages
377
372
  files[:] = modules
378
373
 
379
374
  # We copy over sources and data only; no pyc files.