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.

Files changed (180) hide show
  1. pex/auth.py +1 -1
  2. pex/bin/pex.py +15 -2
  3. pex/build_backend/configuration.py +5 -5
  4. pex/build_backend/wrap.py +27 -23
  5. pex/build_system/pep_517.py +4 -1
  6. pex/cache/dirs.py +17 -12
  7. pex/cli/commands/lock.py +302 -165
  8. pex/cli/commands/pip/core.py +4 -12
  9. pex/cli/commands/pip/wheel.py +1 -1
  10. pex/cli/commands/run.py +13 -20
  11. pex/cli/commands/venv.py +85 -16
  12. pex/cli/pex.py +11 -4
  13. pex/common.py +57 -7
  14. pex/compatibility.py +1 -1
  15. pex/dependency_configuration.py +87 -15
  16. pex/dist_metadata.py +143 -25
  17. pex/docs/html/_pagefind/fragment/en_4250138.pf_fragment +0 -0
  18. pex/docs/html/_pagefind/fragment/en_7125dad.pf_fragment +0 -0
  19. pex/docs/html/_pagefind/fragment/en_785d562.pf_fragment +0 -0
  20. pex/docs/html/_pagefind/fragment/en_8e94bb8.pf_fragment +0 -0
  21. pex/docs/html/_pagefind/fragment/en_a0396bb.pf_fragment +0 -0
  22. pex/docs/html/_pagefind/fragment/en_a8a3588.pf_fragment +0 -0
  23. pex/docs/html/_pagefind/fragment/en_c07d988.pf_fragment +0 -0
  24. pex/docs/html/_pagefind/fragment/en_d718411.pf_fragment +0 -0
  25. pex/docs/html/_pagefind/index/en_a2e3c5e.pf_index +0 -0
  26. pex/docs/html/_pagefind/pagefind-entry.json +1 -1
  27. pex/docs/html/_pagefind/pagefind.en_4ce1afa9e3.pf_meta +0 -0
  28. pex/docs/html/_static/documentation_options.js +1 -1
  29. pex/docs/html/_static/pygments.css +164 -146
  30. pex/docs/html/_static/styles/furo.css +1 -1
  31. pex/docs/html/_static/styles/furo.css.map +1 -1
  32. pex/docs/html/api/vars.html +25 -34
  33. pex/docs/html/buildingpex.html +25 -34
  34. pex/docs/html/genindex.html +24 -33
  35. pex/docs/html/index.html +25 -34
  36. pex/docs/html/recipes.html +25 -34
  37. pex/docs/html/scie.html +25 -34
  38. pex/docs/html/search.html +24 -33
  39. pex/docs/html/whatispex.html +25 -34
  40. pex/entry_points_txt.py +98 -0
  41. pex/environment.py +54 -33
  42. pex/finders.py +1 -1
  43. pex/hashing.py +71 -9
  44. pex/installed_wheel.py +141 -0
  45. pex/interpreter.py +41 -38
  46. pex/interpreter_constraints.py +25 -25
  47. pex/interpreter_implementation.py +40 -0
  48. pex/jobs.py +13 -6
  49. pex/pep_376.py +68 -384
  50. pex/pep_425.py +11 -2
  51. pex/pep_427.py +937 -205
  52. pex/pep_508.py +4 -5
  53. pex/pex_builder.py +5 -8
  54. pex/pex_info.py +14 -9
  55. pex/pip/dependencies/__init__.py +85 -13
  56. pex/pip/dependencies/requires.py +38 -3
  57. pex/pip/foreign_platform/__init__.py +4 -3
  58. pex/pip/installation.py +2 -2
  59. pex/pip/local_project.py +6 -14
  60. pex/pip/package_repositories/__init__.py +78 -0
  61. pex/pip/package_repositories/link_collector.py +96 -0
  62. pex/pip/tool.py +139 -33
  63. pex/pip/vcs.py +109 -43
  64. pex/pip/version.py +8 -1
  65. pex/requirements.py +121 -16
  66. pex/resolve/config.py +5 -1
  67. pex/resolve/configured_resolve.py +32 -10
  68. pex/resolve/configured_resolver.py +10 -39
  69. pex/resolve/downloads.py +4 -3
  70. pex/resolve/lock_downloader.py +16 -23
  71. pex/resolve/lock_resolver.py +41 -51
  72. pex/resolve/locked_resolve.py +89 -32
  73. pex/resolve/locker.py +145 -101
  74. pex/resolve/locker_patches.py +123 -197
  75. pex/resolve/lockfile/create.py +232 -87
  76. pex/resolve/lockfile/download_manager.py +5 -1
  77. pex/resolve/lockfile/json_codec.py +103 -28
  78. pex/resolve/lockfile/model.py +13 -35
  79. pex/resolve/lockfile/pep_751.py +117 -98
  80. pex/resolve/lockfile/requires_dist.py +17 -262
  81. pex/resolve/lockfile/subset.py +11 -0
  82. pex/resolve/lockfile/targets.py +445 -0
  83. pex/resolve/lockfile/updater.py +22 -10
  84. pex/resolve/package_repository.py +406 -0
  85. pex/resolve/pex_repository_resolver.py +1 -1
  86. pex/resolve/pre_resolved_resolver.py +19 -16
  87. pex/resolve/project.py +233 -47
  88. pex/resolve/requirement_configuration.py +28 -10
  89. pex/resolve/resolver_configuration.py +18 -32
  90. pex/resolve/resolver_options.py +234 -28
  91. pex/resolve/resolvers.py +3 -12
  92. pex/resolve/target_options.py +18 -2
  93. pex/resolve/target_system.py +908 -0
  94. pex/resolve/venv_resolver.py +670 -0
  95. pex/resolver.py +673 -209
  96. pex/scie/__init__.py +40 -1
  97. pex/scie/model.py +2 -0
  98. pex/scie/science.py +25 -3
  99. pex/sdist.py +219 -0
  100. pex/sh_boot.py +24 -21
  101. pex/sysconfig.py +5 -3
  102. pex/targets.py +31 -10
  103. pex/third_party/__init__.py +1 -1
  104. pex/tools/commands/repository.py +48 -25
  105. pex/vendor/__init__.py +4 -9
  106. pex/vendor/__main__.py +65 -41
  107. pex/vendor/_vendored/ansicolors/.layout.json +1 -1
  108. pex/vendor/_vendored/ansicolors/ansicolors-1.1.8.dist-info/RECORD +11 -0
  109. pex/vendor/_vendored/ansicolors/ansicolors-1.1.8.pex-info/original-whl-info.json +1 -0
  110. pex/vendor/_vendored/appdirs/.layout.json +1 -1
  111. pex/vendor/_vendored/appdirs/appdirs-1.4.4.dist-info/RECORD +7 -0
  112. pex/vendor/_vendored/appdirs/appdirs-1.4.4.pex-info/original-whl-info.json +1 -0
  113. pex/vendor/_vendored/attrs/.layout.json +1 -1
  114. pex/vendor/_vendored/attrs/attrs-21.5.0.dev0.dist-info/RECORD +37 -0
  115. pex/vendor/_vendored/attrs/attrs-21.5.0.dev0.pex-info/original-whl-info.json +1 -0
  116. pex/vendor/_vendored/packaging_20_9/.layout.json +1 -1
  117. pex/vendor/_vendored/packaging_20_9/packaging-20.9.dist-info/RECORD +20 -0
  118. pex/vendor/_vendored/packaging_20_9/packaging-20.9.pex-info/original-whl-info.json +1 -0
  119. pex/vendor/_vendored/packaging_20_9/pyparsing-2.4.7.dist-info/RECORD +7 -0
  120. pex/vendor/_vendored/packaging_20_9/pyparsing-2.4.7.pex-info/original-whl-info.json +1 -0
  121. pex/vendor/_vendored/packaging_21_3/.layout.json +1 -1
  122. pex/vendor/_vendored/packaging_21_3/packaging-21.3.dist-info/RECORD +20 -0
  123. pex/vendor/_vendored/packaging_21_3/packaging-21.3.pex-info/original-whl-info.json +1 -0
  124. pex/vendor/_vendored/packaging_21_3/pyparsing-3.0.7.dist-info/RECORD +18 -0
  125. pex/vendor/_vendored/packaging_21_3/pyparsing-3.0.7.pex-info/original-whl-info.json +1 -0
  126. pex/vendor/_vendored/packaging_24_0/.layout.json +1 -1
  127. pex/vendor/_vendored/packaging_24_0/packaging-24.0.dist-info/RECORD +22 -0
  128. pex/vendor/_vendored/packaging_24_0/packaging-24.0.pex-info/original-whl-info.json +1 -0
  129. pex/vendor/_vendored/packaging_25_0/.layout.json +1 -1
  130. pex/vendor/_vendored/packaging_25_0/packaging-25.0.dist-info/RECORD +24 -0
  131. pex/vendor/_vendored/packaging_25_0/packaging-25.0.pex-info/original-whl-info.json +1 -0
  132. pex/vendor/_vendored/pip/.layout.json +1 -1
  133. pex/vendor/_vendored/pip/pip/_vendor/certifi/cacert.pem +63 -1
  134. pex/vendor/_vendored/pip/pip-20.3.4.dist-info/RECORD +388 -0
  135. pex/vendor/_vendored/pip/pip-20.3.4.pex-info/original-whl-info.json +1 -0
  136. pex/vendor/_vendored/setuptools/.layout.json +1 -1
  137. pex/vendor/_vendored/setuptools/setuptools-44.0.0+3acb925dd708430aeaf197ea53ac8a752f7c1863.dist-info/RECORD +107 -0
  138. pex/vendor/_vendored/setuptools/setuptools-44.0.0+3acb925dd708430aeaf197ea53ac8a752f7c1863.pex-info/original-whl-info.json +1 -0
  139. pex/vendor/_vendored/toml/.layout.json +1 -1
  140. pex/vendor/_vendored/toml/toml-0.10.2.dist-info/RECORD +11 -0
  141. pex/vendor/_vendored/toml/toml-0.10.2.pex-info/original-whl-info.json +1 -0
  142. pex/vendor/_vendored/tomli/.layout.json +1 -1
  143. pex/vendor/_vendored/tomli/tomli-2.0.1.dist-info/RECORD +10 -0
  144. pex/vendor/_vendored/tomli/tomli-2.0.1.pex-info/original-whl-info.json +1 -0
  145. pex/venv/installer.py +46 -19
  146. pex/venv/venv_pex.py +6 -3
  147. pex/version.py +1 -1
  148. pex/wheel.py +188 -40
  149. pex/whl.py +67 -0
  150. pex/windows/__init__.py +14 -11
  151. {pex-2.54.2.dist-info → pex-2.69.0.dist-info}/METADATA +6 -5
  152. {pex-2.54.2.dist-info → pex-2.69.0.dist-info}/RECORD +157 -133
  153. {pex-2.54.2.dist-info → pex-2.69.0.dist-info}/entry_points.txt +1 -0
  154. {pex-2.54.2.dist-info → pex-2.69.0.dist-info}/pylock/pylock.toml +1 -1
  155. pex/docs/html/_pagefind/fragment/en_42c9d8c.pf_fragment +0 -0
  156. pex/docs/html/_pagefind/fragment/en_45dd5a2.pf_fragment +0 -0
  157. pex/docs/html/_pagefind/fragment/en_4ca74d2.pf_fragment +0 -0
  158. pex/docs/html/_pagefind/fragment/en_77273d5.pf_fragment +0 -0
  159. pex/docs/html/_pagefind/fragment/en_87a59c5.pf_fragment +0 -0
  160. pex/docs/html/_pagefind/fragment/en_8dc89b5.pf_fragment +0 -0
  161. pex/docs/html/_pagefind/fragment/en_9d1319b.pf_fragment +0 -0
  162. pex/docs/html/_pagefind/fragment/en_e55df9d.pf_fragment +0 -0
  163. pex/docs/html/_pagefind/index/en_1e98c6f.pf_index +0 -0
  164. pex/docs/html/_pagefind/pagefind.en_d1c488ecae.pf_meta +0 -0
  165. pex/vendor/_vendored/ansicolors/ansicolors-1.1.8.dist-info/INSTALLER +0 -1
  166. pex/vendor/_vendored/appdirs/appdirs-1.4.4.dist-info/INSTALLER +0 -1
  167. pex/vendor/_vendored/attrs/attrs-21.5.0.dev0.dist-info/INSTALLER +0 -1
  168. pex/vendor/_vendored/packaging_20_9/packaging-20.9.dist-info/INSTALLER +0 -1
  169. pex/vendor/_vendored/packaging_20_9/pyparsing-2.4.7.dist-info/INSTALLER +0 -1
  170. pex/vendor/_vendored/packaging_21_3/packaging-21.3.dist-info/INSTALLER +0 -1
  171. pex/vendor/_vendored/packaging_21_3/pyparsing-3.0.7.dist-info/INSTALLER +0 -1
  172. pex/vendor/_vendored/packaging_24_0/packaging-24.0.dist-info/INSTALLER +0 -1
  173. pex/vendor/_vendored/packaging_25_0/packaging-25.0.dist-info/INSTALLER +0 -1
  174. pex/vendor/_vendored/pip/pip-20.3.4.dist-info/INSTALLER +0 -1
  175. pex/vendor/_vendored/setuptools/setuptools-44.0.0+3acb925dd708430aeaf197ea53ac8a752f7c1863.dist-info/INSTALLER +0 -1
  176. pex/vendor/_vendored/toml/toml-0.10.2.dist-info/INSTALLER +0 -1
  177. pex/vendor/_vendored/tomli/tomli-2.0.1.dist-info/INSTALLER +0 -1
  178. {pex-2.54.2.dist-info → pex-2.69.0.dist-info}/WHEEL +0 -0
  179. {pex-2.54.2.dist-info → pex-2.69.0.dist-info}/licenses/LICENSE +0 -0
  180. {pex-2.54.2.dist-info → pex-2.69.0.dist-info}/top_level.txt +0 -0
pex/pip/tool.py CHANGED
@@ -21,21 +21,21 @@ from pex.common import safe_mkdir, safe_mkdtemp
21
21
  from pex.compatibility import get_stderr_bytes_buffer, shlex_quote, urlparse
22
22
  from pex.dependency_configuration import DependencyConfiguration
23
23
  from pex.dist_metadata import Requirement
24
+ from pex.fetcher import URLFetcher
24
25
  from pex.interpreter import PythonInterpreter
25
26
  from pex.jobs import Job
26
27
  from pex.network_configuration import NetworkConfiguration
27
28
  from pex.pep_427 import install_wheel_interpreter
28
- from pex.pip import dependencies, foreign_platform
29
+ from pex.pep_508 import MarkerEnvironment
30
+ from pex.pip import dependencies, foreign_platform, package_repositories
29
31
  from pex.pip.download_observer import DownloadObserver, PatchSet
30
32
  from pex.pip.log_analyzer import ErrorAnalyzer, ErrorMessage, LogAnalyzer, LogScrapeJob
31
33
  from pex.pip.tailer import Tailer
32
34
  from pex.pip.version import PipVersion, PipVersionValue
33
35
  from pex.platforms import PlatformSpec
34
- from pex.resolve.resolver_configuration import (
35
- BuildConfiguration,
36
- ReposConfiguration,
37
- ResolverVersion,
38
- )
36
+ from pex.resolve.package_repository import ReposConfiguration
37
+ from pex.resolve.resolver_configuration import BuildConfiguration, ResolverVersion
38
+ from pex.resolve.target_system import UniversalTarget
39
39
  from pex.targets import Target
40
40
  from pex.tracer import TRACER
41
41
  from pex.typing import TYPE_CHECKING
@@ -54,7 +54,9 @@ if TYPE_CHECKING:
54
54
  Match,
55
55
  Optional,
56
56
  Sequence,
57
+ Text,
57
58
  Tuple,
59
+ Union,
58
60
  )
59
61
 
60
62
  import attr # vendor:skip
@@ -64,8 +66,8 @@ else:
64
66
 
65
67
  @attr.s(frozen=True)
66
68
  class PipArgs(object):
67
- indexes = attr.ib(default=None) # type: Optional[Sequence[str]]
68
- find_links = attr.ib(default=None) # type: Optional[Iterable[str]]
69
+ indexes = attr.ib(default=None) # type: Optional[Sequence[Text]]
70
+ find_links = attr.ib(default=None) # type: Optional[Iterable[Text]]
69
71
  network_configuration = attr.ib(default=None) # type: Optional[NetworkConfiguration]
70
72
 
71
73
  def iter(self, version):
@@ -152,10 +154,8 @@ class PackageIndexConfiguration(object):
152
154
  cls,
153
155
  pip_version=None, # type: Optional[PipVersionValue]
154
156
  resolver_version=None, # type: Optional[ResolverVersion.Value]
155
- indexes=None, # type: Optional[Sequence[str]]
156
- find_links=None, # type: Optional[Iterable[str]]
157
+ repos_configuration=ReposConfiguration(), # type: ReposConfiguration
157
158
  network_configuration=None, # type: Optional[NetworkConfiguration]
158
- password_entries=(), # type: Iterable[PasswordEntry]
159
159
  use_pip_config=False, # type: bool
160
160
  extra_pip_requirements=(), # type: Tuple[Requirement, ...]
161
161
  keyring_provider=None, # type: Optional[str]
@@ -174,8 +174,8 @@ class PackageIndexConfiguration(object):
174
174
  resolver_version=resolver_version,
175
175
  network_configuration=network_configuration,
176
176
  args=PipArgs(
177
- indexes=indexes,
178
- find_links=find_links,
177
+ indexes=repos_configuration.indexes,
178
+ find_links=repos_configuration.find_links,
179
179
  network_configuration=network_configuration,
180
180
  ),
181
181
  env=cls._calculate_env(
@@ -183,7 +183,7 @@ class PackageIndexConfiguration(object):
183
183
  ),
184
184
  use_pip_config=use_pip_config,
185
185
  extra_pip_requirements=extra_pip_requirements,
186
- password_entries=password_entries,
186
+ repos_configuration=repos_configuration,
187
187
  keyring_provider=keyring_provider,
188
188
  )
189
189
 
@@ -194,7 +194,7 @@ class PackageIndexConfiguration(object):
194
194
  args, # type: PipArgs
195
195
  env, # type: Iterable[Tuple[str, str]]
196
196
  use_pip_config, # type: bool
197
- password_entries=(), # type: Iterable[PasswordEntry]
197
+ repos_configuration=ReposConfiguration(), # type: ReposConfiguration
198
198
  pip_version=None, # type: Optional[PipVersionValue]
199
199
  extra_pip_requirements=(), # type: Tuple[Requirement, ...]
200
200
  keyring_provider=None, # type: Optional[str]
@@ -205,11 +205,32 @@ class PackageIndexConfiguration(object):
205
205
  self.args = args # type: PipArgs
206
206
  self.env = dict(env) # type: Mapping[str, str]
207
207
  self.use_pip_config = use_pip_config # type: bool
208
- self.password_entries = password_entries # type: Iterable[PasswordEntry]
208
+ self.repos_configuration = repos_configuration
209
209
  self.pip_version = pip_version # type: Optional[PipVersionValue]
210
210
  self.extra_pip_requirements = extra_pip_requirements # type: Tuple[Requirement, ...]
211
211
  self.keyring_provider = keyring_provider # type: Optional[str]
212
212
 
213
+ @property
214
+ def password_entries(self):
215
+ # type: () -> Iterable[PasswordEntry]
216
+ return self.repos_configuration.password_entries
217
+
218
+ def patch(
219
+ self,
220
+ pip_version, # type: PipVersionValue
221
+ target, # type: Union[UniversalTarget, MarkerEnvironment]
222
+ requirement_files=None, # type: Optional[Iterable[str]]
223
+ ):
224
+ # type: (...) -> Optional[DownloadObserver]
225
+ return package_repositories.patch(
226
+ repos_configuration=self.repos_configuration.with_contained_repos(
227
+ requirement_files,
228
+ fetcher=URLFetcher(network_configuration=self.network_configuration),
229
+ ),
230
+ pip_version=pip_version,
231
+ target=target,
232
+ )
233
+
213
234
 
214
235
  if TYPE_CHECKING:
215
236
  from pex.pip.log_analyzer import ErrorAnalysis
@@ -455,7 +476,7 @@ class Pip(object):
455
476
 
456
477
  command = pip_args + list(args)
457
478
 
458
- # N.B.: Package index options in Pep always have the same option names, but they are
479
+ # N.B.: Package index options in Pip always have the same option names, but they are
459
480
  # registered as subcommand-specific, so we must append them here _after_ the pip subcommand
460
481
  # specified in `args`.
461
482
  if package_index_configuration:
@@ -470,9 +491,7 @@ class Pip(object):
470
491
  # since Pip relies upon `shutil.move` which is only atomic when `os.rename` can be used.
471
492
  # See https://github.com/pex-tool/pex/issues/1776 for an example of the issues non-atomic
472
493
  # moves lead to in the `pip wheel` case.
473
- pip_tmpdir = os.path.join(self.cache_dir, ".tmp")
474
- safe_mkdir(pip_tmpdir)
475
- extra_env.update(TMPDIR=pip_tmpdir)
494
+ extra_env.update(TMPDIR=safe_mkdtemp(dir=safe_mkdir(self.cache_dir), prefix=".tmp."))
476
495
 
477
496
  with ENV.strip().patch(
478
497
  PEX_ROOT=ENV.PEX_ROOT,
@@ -533,8 +552,7 @@ class Pip(object):
533
552
  )
534
553
  return Job(command=command, process=process, finalizer=finalizer, context="pip")
535
554
 
536
- @staticmethod
537
- def _iter_build_configuration_options(build_configuration):
555
+ def _iter_build_configuration_options(self, build_configuration):
538
556
  # type: (BuildConfiguration) -> Iterator[str]
539
557
 
540
558
  # N.B.: BuildConfiguration maintains invariants that ensure --only-binary, --no-binary,
@@ -557,12 +575,54 @@ class Pip(object):
557
575
  if build_configuration.prefer_older_binary:
558
576
  yield "--prefer-binary"
559
577
 
560
- if build_configuration.use_pep517 is not None:
578
+ # N.B.: In 25.3 `--use-pep517` became the default and only option.
579
+ if build_configuration.use_pep517 is not None and self.version < PipVersion.v25_3:
561
580
  yield "--use-pep517" if build_configuration.use_pep517 else "--no-use-pep517"
562
581
 
563
582
  if not build_configuration.build_isolation:
564
583
  yield "--no-build-isolation"
565
584
 
585
+ def spawn_report(
586
+ self,
587
+ report_path, # type: str
588
+ requirements=None, # type: Optional[Iterable[str]]
589
+ requirement_files=None, # type: Optional[Iterable[str]]
590
+ constraint_files=None, # type: Optional[Iterable[str]]
591
+ allow_prereleases=False, # type: bool
592
+ transitive=True, # type: bool
593
+ target=None, # type: Optional[Target]
594
+ package_index_configuration=None, # type: Optional[PackageIndexConfiguration]
595
+ build_configuration=BuildConfiguration(), # type: BuildConfiguration
596
+ observer=None, # type: Optional[DownloadObserver]
597
+ dependency_configuration=DependencyConfiguration(), # type: DependencyConfiguration
598
+ universal_target=None, # type: Optional[UniversalTarget]
599
+ log=None, # type: Optional[str]
600
+ ):
601
+ # type: (...) -> Job
602
+ report_cmd = [
603
+ "install",
604
+ "--no-clean",
605
+ "--dry-run",
606
+ "--ignore-installed",
607
+ "--report",
608
+ report_path,
609
+ ]
610
+ return self._spawn_install_compatible_command(
611
+ cmd=report_cmd,
612
+ requirements=requirements,
613
+ requirement_files=requirement_files,
614
+ constraint_files=constraint_files,
615
+ allow_prereleases=allow_prereleases,
616
+ transitive=transitive,
617
+ target=target,
618
+ package_index_configuration=package_index_configuration,
619
+ build_configuration=build_configuration,
620
+ observer=observer,
621
+ dependency_configuration=dependency_configuration,
622
+ universal_target=universal_target,
623
+ log=log,
624
+ )
625
+
566
626
  def spawn_download_distributions(
567
627
  self,
568
628
  download_dir, # type: str
@@ -576,35 +636,70 @@ class Pip(object):
576
636
  build_configuration=BuildConfiguration(), # type: BuildConfiguration
577
637
  observer=None, # type: Optional[DownloadObserver]
578
638
  dependency_configuration=DependencyConfiguration(), # type: DependencyConfiguration
639
+ universal_target=None, # type: Optional[UniversalTarget]
579
640
  log=None, # type: Optional[str]
580
641
  ):
581
642
  # type: (...) -> Job
582
- target = target or targets.current()
583
643
 
584
644
  download_cmd = ["download", "--dest", download_dir]
645
+ return self._spawn_install_compatible_command(
646
+ cmd=download_cmd,
647
+ requirements=requirements,
648
+ requirement_files=requirement_files,
649
+ constraint_files=constraint_files,
650
+ allow_prereleases=allow_prereleases,
651
+ transitive=transitive,
652
+ target=target,
653
+ package_index_configuration=package_index_configuration,
654
+ build_configuration=build_configuration,
655
+ observer=observer,
656
+ dependency_configuration=dependency_configuration,
657
+ universal_target=universal_target,
658
+ log=log,
659
+ )
660
+
661
+ def _spawn_install_compatible_command(
662
+ self,
663
+ cmd, # type: List[str]
664
+ requirements=None, # type: Optional[Iterable[str]]
665
+ requirement_files=None, # type: Optional[Iterable[str]]
666
+ constraint_files=None, # type: Optional[Iterable[str]]
667
+ allow_prereleases=False, # type: bool
668
+ transitive=True, # type: bool
669
+ target=None, # type: Optional[Target]
670
+ package_index_configuration=None, # type: Optional[PackageIndexConfiguration]
671
+ build_configuration=BuildConfiguration(), # type: BuildConfiguration
672
+ observer=None, # type: Optional[DownloadObserver]
673
+ dependency_configuration=DependencyConfiguration(), # type: DependencyConfiguration
674
+ universal_target=None, # type: Optional[UniversalTarget]
675
+ log=None, # type: Optional[str]
676
+ ):
677
+ # type: (...) -> Job
678
+ target = target or targets.current()
679
+
585
680
  extra_env = {} # type: Dict[str, str]
586
681
  pex_extra_sys_path = [] # type: List[str]
587
682
 
588
- download_cmd.extend(self._iter_build_configuration_options(build_configuration))
683
+ cmd.extend(self._iter_build_configuration_options(build_configuration))
589
684
  if not build_configuration.build_isolation:
590
685
  pex_extra_sys_path.extend(sys.path)
591
686
 
592
687
  if allow_prereleases:
593
- download_cmd.append("--pre")
688
+ cmd.append("--pre")
594
689
 
595
690
  if not transitive:
596
- download_cmd.append("--no-deps")
691
+ cmd.append("--no-deps")
597
692
 
598
693
  if requirement_files:
599
694
  for requirement_file in requirement_files:
600
- download_cmd.extend(["--requirement", requirement_file])
695
+ cmd.extend(["--requirement", requirement_file])
601
696
 
602
697
  if constraint_files:
603
698
  for constraint_file in constraint_files:
604
- download_cmd.extend(["--constraint", constraint_file])
699
+ cmd.extend(["--constraint", constraint_file])
605
700
 
606
701
  if requirements:
607
- download_cmd.extend(requirements)
702
+ cmd.extend(requirements)
608
703
 
609
704
  foreign_platform_observer = foreign_platform.patch(target)
610
705
  if (
@@ -623,7 +718,18 @@ class Pip(object):
623
718
  for obs in (
624
719
  foreign_platform_observer,
625
720
  observer,
626
- dependencies.patch(dependency_configuration),
721
+ dependencies.patch(
722
+ dependency_configuration, target=universal_target or target.marker_environment
723
+ ),
724
+ (
725
+ package_index_configuration.patch(
726
+ pip_version=self.version,
727
+ target=universal_target or target.marker_environment,
728
+ requirement_files=requirement_files,
729
+ )
730
+ if package_index_configuration
731
+ else None
732
+ ),
627
733
  ):
628
734
  if obs:
629
735
  if obs.analyzer:
@@ -689,7 +795,7 @@ class Pip(object):
689
795
  tailer.stop()
690
796
 
691
797
  command, process = self._spawn_pip_isolated(
692
- download_cmd,
798
+ cmd,
693
799
  package_index_configuration=package_index_configuration,
694
800
  interpreter=target.get_interpreter(),
695
801
  log=log,
@@ -717,7 +823,7 @@ class Pip(object):
717
823
  build_configuration=BuildConfiguration.create(allow_builds=False),
718
824
  ).wait()
719
825
  for wheel in glob.glob(os.path.join(atomic_dir.work_dir, "*.whl")):
720
- install_wheel_interpreter(wheel_path=wheel, interpreter=pip_interpreter)
826
+ install_wheel_interpreter(wheel=wheel, interpreter=pip_interpreter)
721
827
 
722
828
  def spawn_build_wheels(
723
829
  self,
pex/pip/vcs.py CHANGED
@@ -8,20 +8,37 @@ import re
8
8
 
9
9
  from pex import hashing
10
10
  from pex.artifact_url import VCS, Fingerprint
11
- from pex.common import is_pyc_dir, is_pyc_file, open_zip, temporary_dir
11
+ from pex.common import is_pyc_dir, is_pyc_file
12
+ from pex.exceptions import reportable_unexpected_error_msg
12
13
  from pex.hashing import Sha256
13
14
  from pex.pep_440 import Version
14
15
  from pex.pep_503 import ProjectName
15
16
  from pex.result import Error, try_
16
- from pex.tracer import TRACER
17
17
  from pex.typing import TYPE_CHECKING
18
18
 
19
19
  if TYPE_CHECKING:
20
- from typing import Optional, Tuple, Union
20
+ # N.B.: The `re.Pattern` type is not available in all Python versions Pex supports.
21
+ from re import Pattern # type: ignore[attr-defined]
22
+ from typing import Callable, Optional, Text, Tuple, Union
21
23
 
22
24
  from pex.hashing import HintedDigest
23
25
 
24
26
 
27
+ def _project_name_re(project_name):
28
+ # type: (ProjectName) -> str
29
+ return project_name.normalized.replace("-", "[-_.]+")
30
+
31
+
32
+ def _built_source_dist_pattern(project_name):
33
+ # type: (ProjectName) -> Pattern
34
+ return re.compile(
35
+ r"(?P<project_name>{project_name_re})-(?P<version>.+)\.zip".format(
36
+ project_name_re=_project_name_re(project_name)
37
+ ),
38
+ re.IGNORECASE,
39
+ )
40
+
41
+
25
42
  def _find_built_source_dist(
26
43
  build_dir, # type: str
27
44
  project_name, # type: ProjectName
@@ -33,12 +50,7 @@ def _find_built_source_dist(
33
50
  # encoded in: `pip._internal.req.req_install.InstallRequirement.archive`.
34
51
 
35
52
  listing = os.listdir(build_dir)
36
- pattern = re.compile(
37
- r"{project_name}-(?P<version>.+)\.zip".format(
38
- project_name=project_name.normalized.replace("-", "[-_.]+")
39
- ),
40
- re.IGNORECASE,
41
- )
53
+ pattern = _built_source_dist_pattern(project_name)
42
54
  for name in listing:
43
55
  match = pattern.match(name)
44
56
  if match and Version(match.group("version")) == version:
@@ -57,57 +69,111 @@ def _find_built_source_dist(
57
69
 
58
70
  def fingerprint_downloaded_vcs_archive(
59
71
  download_dir, # type: str
60
- project_name, # type: str
61
- version, # type: str
72
+ project_name, # type: ProjectName
73
+ version, # type: Version
62
74
  vcs, # type: VCS.Value
63
- subdirectory=None, # type: Optional[str]
64
75
  ):
65
76
  # type: (...) -> Tuple[Fingerprint, str]
66
77
 
67
78
  archive_path = try_(
68
- _find_built_source_dist(
69
- build_dir=download_dir, project_name=ProjectName(project_name), version=Version(version)
70
- )
79
+ _find_built_source_dist(build_dir=download_dir, project_name=project_name, version=version)
71
80
  )
72
81
  digest = Sha256()
73
- digest_vcs_archive(archive_path=archive_path, vcs=vcs, digest=digest, subdirectory=subdirectory)
82
+ digest_vcs_archive(project_name=project_name, archive_path=archive_path, vcs=vcs, digest=digest)
74
83
  return Fingerprint.from_digest(digest), archive_path
75
84
 
76
85
 
86
+ def _vcs_dir_filter(
87
+ vcs, # type: VCS.Value
88
+ project_name, # type: ProjectName
89
+ ):
90
+ # type: (...) -> Callable[[Text], bool]
91
+
92
+ # Ignore VCS control directories for the purposes of fingerprinting the version controlled
93
+ # source tree. VCS control directories can contain non-reproducible content (Git at least
94
+ # has files that contain timestamps).
95
+ #
96
+ # We cannot prune these directories from the source archive directly unfortunately since
97
+ # some build processes use VCS version information to derive their version numbers (C.F.:
98
+ # https://pypi.org/project/setuptools-scm/). As such, we'll get a stable fingerprint, but be
99
+ # forced to re-build a wheel each time the VCS requirement is re-locked later, even when it
100
+ # hashes the same.
101
+ vcs_control_dir = ".{vcs}".format(vcs=vcs)
102
+
103
+ # N.B.: If the VCS project uses setuptools as its build backend, depending on the version of
104
+ # Pip used, the VCS checkout can have a `<project name>.egg-info/` directory littering its root
105
+ # left over from Pip generating project metadata to determine version and dependencies. No other
106
+ # well known build-backend has this problem at this time (checked hatchling, poetry-core,
107
+ # pdm-backend and uv_build).
108
+ # C.F.: https://github.com/pypa/pip/pull/13602
109
+ egg_info_dir_re = re.compile(
110
+ r"^{project_name_re}\.egg-info$".format(project_name_re=_project_name_re(project_name)),
111
+ re.IGNORECASE,
112
+ )
113
+
114
+ def vcs_dir_filter(dir_path):
115
+ # type: (Text) -> bool
116
+ if is_pyc_dir(dir_path):
117
+ return False
118
+
119
+ base_dir_name = dir_path.split(os.sep)[0]
120
+ return base_dir_name != vcs_control_dir and not egg_info_dir_re.match(base_dir_name)
121
+
122
+ return vcs_dir_filter
123
+
124
+
125
+ def _vcs_file_filter(vcs):
126
+ # type: (VCS.Value) -> Callable[[Text], bool]
127
+ return lambda f: not is_pyc_file(f)
128
+
129
+
77
130
  def digest_vcs_archive(
131
+ project_name, # type: ProjectName
78
132
  archive_path, # type: str
79
133
  vcs, # type: VCS.Value
80
134
  digest, # type: HintedDigest
81
- subdirectory=None, # type: Optional[str]
82
135
  ):
83
136
  # type: (...) -> None
84
137
 
85
138
  # All VCS requirements are prepared as zip archives as encoded in:
86
- # `pip._internal.req.req_install.InstallRequirement.archive`.
87
- with TRACER.timed(
88
- "Digesting {archive} {vcs} archive".format(archive=os.path.basename(archive_path), vcs=vcs)
89
- ), temporary_dir() as chroot, open_zip(archive_path) as archive:
90
- archive.extractall(chroot)
91
-
92
- # Ignore VCS control directories for the purposes of fingerprinting the version controlled
93
- # source tree. VCS control directories can contain non-reproducible content (Git at least
94
- # has files that contain timestamps).
95
- #
96
- # We cannot prune these directories from the source archive directly unfortunately since
97
- # some build processes use VCS version information to derive their version numbers (C.F.:
98
- # https://pypi.org/project/setuptools-scm/). As such, we'll get a stable fingerprint, but be
99
- # forced to re-build a wheel each time the VCS requirement is re-locked later, even when it
100
- # hashes the same.
101
- vcs_control_dir = ".{vcs}".format(vcs=vcs)
102
-
103
- # TODO(John Sirois): Consider implementing zip_hash to avoid the extractall.
104
- hashing.dir_hash(
105
- directory=os.path.join(chroot, subdirectory) if subdirectory else chroot,
106
- digest=digest,
107
- dir_filter=(
108
- lambda dir_path: (
109
- not is_pyc_dir(dir_path) and os.path.basename(dir_path) != vcs_control_dir
139
+ # `pip._internal.req.req_install.InstallRequirement.archive` and the archive is already offset
140
+ # by a subdirectory (if any).
141
+
142
+ # The zip archives created by Pip have a single project name top-level directory housing
143
+ # the full clone. We look for that to get a consistent clone hash with a bare clone.
144
+ match = _built_source_dist_pattern(project_name).match(os.path.basename(archive_path))
145
+ if match is None:
146
+ raise AssertionError(
147
+ reportable_unexpected_error_msg(
148
+ "Failed to determine the project name prefix for the VCS zip {zip} with expected "
149
+ "canonical project name {project_name}".format(
150
+ zip=archive_path, project_name=project_name
110
151
  )
111
- ),
112
- file_filter=lambda f: not is_pyc_file(f),
152
+ )
113
153
  )
154
+ top_dir = match.group("project_name")
155
+
156
+ hashing.zip_hash(
157
+ zip_path=archive_path,
158
+ digest=digest,
159
+ relpath=top_dir,
160
+ dir_filter=_vcs_dir_filter(vcs, project_name),
161
+ file_filter=_vcs_file_filter(vcs),
162
+ )
163
+
164
+
165
+ def digest_vcs_repo(
166
+ project_name, # type: ProjectName
167
+ repo_path, # type: str
168
+ vcs, # type: VCS.Value
169
+ digest, # type: HintedDigest
170
+ subdirectory=None, # type: Optional[str]
171
+ ):
172
+ # type: (...) -> None
173
+
174
+ hashing.dir_hash(
175
+ directory=os.path.join(repo_path, subdirectory) if subdirectory else repo_path,
176
+ digest=digest,
177
+ dir_filter=_vcs_dir_filter(vcs, project_name),
178
+ file_filter=_vcs_file_filter(vcs),
179
+ )
pex/pip/version.py CHANGED
@@ -366,7 +366,14 @@ class PipVersion(Enum["PipVersionValue"]):
366
366
  version="25.2",
367
367
  setuptools_version="80.9.0",
368
368
  wheel_version="0.45.1",
369
- requires_python=">=3.9,<3.15",
369
+ requires_python=">=3.9,<3.16",
370
+ )
371
+
372
+ v25_3 = PipVersionValue(
373
+ version="25.3",
374
+ setuptools_version="80.9.0",
375
+ wheel_version="0.45.1",
376
+ requires_python=">=3.9,<3.16",
370
377
  )
371
378
 
372
379
  VENDORED = v20_3_4_patched