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
@@ -0,0 +1,670 @@
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 functools
7
+ import hashlib
8
+ import os
9
+ from collections import defaultdict, deque
10
+
11
+ from pex import pex_warnings
12
+ from pex.atomic_directory import atomic_directory
13
+ from pex.cache.dirs import CacheDir, InstalledWheelDir
14
+ from pex.common import pluralize, safe_relative_symlink
15
+ from pex.compatibility import commonpath
16
+ from pex.dependency_configuration import DependencyConfiguration
17
+ from pex.dist_metadata import (
18
+ Constraint,
19
+ Distribution,
20
+ DistributionType,
21
+ MetadataType,
22
+ Requirement,
23
+ find_distribution,
24
+ )
25
+ from pex.exceptions import production_assert, reportable_unexpected_error_msg
26
+ from pex.fingerprinted_distribution import FingerprintedDistribution
27
+ from pex.installed_wheel import InstalledWheel
28
+ from pex.jobs import DEFAULT_MAX_JOBS, iter_map_parallel
29
+ from pex.orderedset import OrderedSet
30
+ from pex.pep_376 import InstalledDirectory, InstalledFile, Record
31
+ from pex.pep_427 import InstallableType, InstallableWheel, InstallPaths, install_wheel_chroot
32
+ from pex.pep_503 import ProjectName
33
+ from pex.pip.version import PipVersion
34
+ from pex.requirements import LocalProjectRequirement
35
+ from pex.resolve.configured_resolver import ConfiguredResolver
36
+ from pex.resolve.requirement_configuration import RequirementConfiguration
37
+ from pex.resolve.resolver_configuration import PipConfiguration
38
+ from pex.resolve.resolvers import ResolvedDistribution, Resolver, ResolveResult
39
+ from pex.result import Error
40
+ from pex.sysconfig import script_name
41
+ from pex.targets import LocalInterpreter, Target, Targets
42
+ from pex.typing import TYPE_CHECKING
43
+ from pex.venv.virtualenv import Virtualenv
44
+ from pex.wheel import WHEEL, Wheel
45
+ from pex.whl import repacked_whl
46
+
47
+ if TYPE_CHECKING:
48
+ from typing import (
49
+ DefaultDict,
50
+ Deque,
51
+ FrozenSet,
52
+ Iterable,
53
+ Iterator,
54
+ List,
55
+ Mapping,
56
+ Set,
57
+ Tuple,
58
+ Union,
59
+ )
60
+
61
+ import attr # vendor:skip
62
+ else:
63
+ import pex.third_party.attr as attr
64
+
65
+
66
+ def _normalize_record(
67
+ distribution, # type: Distribution
68
+ install_paths, # type: InstallPaths
69
+ record_data, # type: bytes
70
+ ):
71
+ # type: (...) -> bytes
72
+
73
+ entry_map = distribution.get_entry_map()
74
+ entry_point_scripts = {
75
+ script_name(entry_point)
76
+ for key in ("console_scripts", "gui_scripts")
77
+ for entry_point in entry_map.get(key, {})
78
+ }
79
+ if not entry_point_scripts:
80
+ return record_data
81
+
82
+ scripts_dir = os.path.realpath(install_paths.scripts)
83
+ record_lines = record_data.decode("utf-8").splitlines(True) # N.B. no kw in 2.7: keepends=True
84
+ eol = os.sep
85
+ if record_lines:
86
+ eol = "\r\n" if record_lines[0].endswith("\r\n") else "\n"
87
+
88
+ installed_files = [] # type: List[Union[InstalledFile, InstalledDirectory]]
89
+ for installed_file in Record.read(lines=iter(record_lines)):
90
+ if isinstance(installed_file, InstalledDirectory):
91
+ installed_files.append(installed_file)
92
+ elif isinstance(installed_file, InstalledFile) and (
93
+ (os.path.basename(installed_file.path) not in entry_point_scripts)
94
+ or (
95
+ scripts_dir
96
+ != commonpath(
97
+ (
98
+ scripts_dir,
99
+ os.path.realpath(os.path.join(distribution.location, installed_file.path)),
100
+ )
101
+ )
102
+ )
103
+ ):
104
+ installed_files.append(installed_file)
105
+ return Record.write_bytes(installed_files=installed_files, eol=eol)
106
+
107
+
108
+ def _install_distribution(
109
+ venv_distribution, # type: VenvDistribution
110
+ result_type, # type: InstallableType.Value
111
+ use_system_time, # type: bool
112
+ ):
113
+ # type: (...) -> ResolvedDistribution
114
+
115
+ interpreter = venv_distribution.target.interpreter
116
+ distribution = venv_distribution.distribution
117
+
118
+ production_assert(distribution.type is DistributionType.INSTALLED)
119
+ production_assert(distribution.metadata.files.metadata.type is MetadataType.DIST_INFO)
120
+
121
+ venv_install_paths = InstallPaths.interpreter(
122
+ interpreter,
123
+ project_name=distribution.metadata.project_name,
124
+ root_is_purelib=WHEEL.from_distribution(distribution).root_is_purelib,
125
+ )
126
+ wheel = InstallableWheel.from_whl(
127
+ whl=Wheel.from_distribution(distribution), install_paths=venv_install_paths
128
+ )
129
+ record_data = wheel.metadata_files.read("RECORD")
130
+ if not record_data:
131
+ raise AssertionError(
132
+ reportable_unexpected_error_msg(
133
+ "Distribution for {project_name} {version} installed at {location} "
134
+ "unexpectedly has no installation RECORD.",
135
+ project_name=distribution.project_name,
136
+ version=distribution.version,
137
+ location=distribution.location,
138
+ )
139
+ )
140
+
141
+ installed_wheel_dir = InstalledWheelDir.create(
142
+ wheel_name=wheel.wheel_file_name,
143
+ install_hash=hashlib.sha256(
144
+ _normalize_record(
145
+ distribution=Distribution(location=wheel.location, metadata=wheel.dist_metadata()),
146
+ install_paths=venv_install_paths,
147
+ record_data=record_data,
148
+ )
149
+ ).hexdigest(),
150
+ )
151
+ with atomic_directory(target_dir=installed_wheel_dir) as atomic_dir:
152
+ if not atomic_dir.is_finalized():
153
+ installed_wheel = install_wheel_chroot(
154
+ wheel,
155
+ atomic_dir.work_dir,
156
+ )
157
+ if not installed_wheel.fingerprint:
158
+ raise AssertionError(reportable_unexpected_error_msg())
159
+ runtime_key_dir = CacheDir.INSTALLED_WHEELS.path(installed_wheel.fingerprint)
160
+ with atomic_directory(runtime_key_dir) as symlink_atomic_dir:
161
+ if not symlink_atomic_dir.is_finalized():
162
+ # Note: Create a relative path symlink between the two directories so that the
163
+ # PEX_ROOT can be used within a chroot environment where the prefix of the path
164
+ # may change between programs running inside and outside the chroot.
165
+ safe_relative_symlink(
166
+ installed_wheel_dir,
167
+ os.path.join(symlink_atomic_dir.work_dir, wheel.wheel_file_name),
168
+ )
169
+
170
+ installed_wheel = InstalledWheel.load(installed_wheel_dir)
171
+ if not installed_wheel.fingerprint:
172
+ raise AssertionError(reportable_unexpected_error_msg())
173
+
174
+ if result_type is InstallableType.INSTALLED_WHEEL_CHROOT:
175
+ return ResolvedDistribution(
176
+ target=venv_distribution.target,
177
+ fingerprinted_distribution=FingerprintedDistribution(
178
+ distribution=Distribution.load(installed_wheel.prefix_dir),
179
+ fingerprint=installed_wheel.fingerprint,
180
+ ),
181
+ direct_requirements=venv_distribution.direct_requirements,
182
+ )
183
+
184
+ return ResolvedDistribution(
185
+ target=venv_distribution.target,
186
+ fingerprinted_distribution=repacked_whl(
187
+ installed_wheel,
188
+ fingerprint=installed_wheel.fingerprint,
189
+ use_system_time=use_system_time,
190
+ ),
191
+ direct_requirements=venv_distribution.direct_requirements,
192
+ )
193
+
194
+
195
+ @attr.s(frozen=True)
196
+ class VenvDistribution(object):
197
+ target = attr.ib() # type: LocalInterpreter
198
+ distribution = attr.ib() # type: Distribution
199
+ direct_requirements = attr.ib() # type: Iterable[Requirement]
200
+
201
+
202
+ def _install_venv_distributions(
203
+ venv_resolve_results, # type: Iterable[VenvResolveResult]
204
+ max_install_jobs=DEFAULT_MAX_JOBS, # type: int
205
+ result_type=InstallableType.INSTALLED_WHEEL_CHROOT, # type: InstallableType.Value
206
+ use_system_time=False, # type: bool
207
+ ):
208
+ # type: (...) -> Iterator[ResolvedDistribution]
209
+
210
+ seen = set() # type: Set[str]
211
+
212
+ venv_distributions = [] # type: List[VenvDistribution]
213
+ for venv_resolve_result in venv_resolve_results:
214
+ target = venv_resolve_result.target
215
+ direct_requirements = venv_resolve_result.direct_requirements_by_project_name
216
+ for re_resolved_distribution in venv_resolve_result.re_resolved_distributions:
217
+ wheel_file_name = Wheel.from_distribution(
218
+ re_resolved_distribution.distribution
219
+ ).wheel_file_name
220
+ if wheel_file_name in seen:
221
+ continue
222
+
223
+ seen.add(wheel_file_name)
224
+ yield ResolvedDistribution(
225
+ target=target,
226
+ fingerprinted_distribution=re_resolved_distribution,
227
+ direct_requirements=direct_requirements.get(
228
+ re_resolved_distribution.project_name, ()
229
+ ),
230
+ )
231
+ for venv_distribution in venv_resolve_result.venv_distributions:
232
+ wheel_file_name = Wheel.from_distribution(venv_distribution).wheel_file_name
233
+ if wheel_file_name in seen:
234
+ continue
235
+
236
+ seen.add(wheel_file_name)
237
+ venv_distributions.append(
238
+ VenvDistribution(
239
+ target=target,
240
+ distribution=venv_distribution,
241
+ direct_requirements=direct_requirements.get(
242
+ venv_distribution.metadata.project_name, ()
243
+ ),
244
+ )
245
+ )
246
+
247
+ for resolved_distribution in iter_map_parallel(
248
+ inputs=venv_distributions,
249
+ function=functools.partial(
250
+ _install_distribution, result_type=result_type, use_system_time=use_system_time
251
+ ),
252
+ max_jobs=max_install_jobs,
253
+ ):
254
+ yield resolved_distribution
255
+
256
+
257
+ @attr.s(frozen=True)
258
+ class ResolveRequirement(object):
259
+ requirement = attr.ib() # type: Requirement
260
+ activated_extras = attr.ib(default=frozenset()) # type: FrozenSet[str]
261
+ required_by = attr.ib(default=None) # type: ResolveRequirement
262
+
263
+ @property
264
+ def project_name(self):
265
+ # type: () -> ProjectName
266
+ return self.requirement.project_name
267
+
268
+ def applies(
269
+ self,
270
+ target, # type: Target
271
+ dependency_configuration=DependencyConfiguration(), # type: DependencyConfiguration
272
+ ):
273
+ # type: (...) -> bool
274
+
275
+ if dependency_configuration.excluded_by(self.requirement):
276
+ return False
277
+
278
+ return target.requirement_applies(
279
+ requirement=self.requirement, extras=self.activated_extras
280
+ )
281
+
282
+ def contains(
283
+ self,
284
+ distribution, # type: Distribution
285
+ prereleases=False, # type: bool
286
+ ):
287
+ # type: (...) -> bool
288
+ return self.requirement.contains(distribution, prereleases=prereleases)
289
+
290
+ def dependency(
291
+ self,
292
+ requirement, # type: Requirement
293
+ target, # type: Target
294
+ dependency_configuration=DependencyConfiguration(), # type: DependencyConfiguration
295
+ ):
296
+ # type: (...) -> ResolveRequirement
297
+ return ResolveRequirement(
298
+ requirement=dependency_configuration.overridden_by(requirement, target) or requirement,
299
+ activated_extras=self.requirement.extras,
300
+ required_by=self,
301
+ )
302
+
303
+ def __str__(self):
304
+ # type: () -> str
305
+
306
+ if not self.required_by:
307
+ return "top level requirement {requirement}".format(requirement=self.requirement)
308
+
309
+ return "{required_by} -> {requirement}".format(
310
+ required_by=self.required_by, requirement=self.requirement
311
+ )
312
+
313
+
314
+ def _resolve_distributions(
315
+ venv, # type: Virtualenv
316
+ resolver, # type: Resolver
317
+ target, # type: Target
318
+ search_path, # type: Iterable[str]
319
+ requirements, # type: Iterable[Requirement]
320
+ constraints_by_project_name, # type: Mapping[ProjectName, Iterable[Constraint]]
321
+ dependency_configuration=DependencyConfiguration(), # type: DependencyConfiguration
322
+ allow_prereleases=False, # type: bool
323
+ compile=False, # type: bool
324
+ ignore_errors=False, # type: bool
325
+ result_type=InstallableType.INSTALLED_WHEEL_CHROOT, # type: InstallableType.Value
326
+ ):
327
+ # type: (...) -> Iterator[Union[Distribution, FingerprintedDistribution, Error]]
328
+
329
+ def meets_requirement(
330
+ selected_distribution, # type: Distribution
331
+ requirement, # type: ResolveRequirement
332
+ ):
333
+ # type: (...) -> bool
334
+
335
+ if not requirement.contains(selected_distribution, prereleases=allow_prereleases):
336
+ return False
337
+
338
+ constraints = [
339
+ constraint
340
+ for constraint in constraints_by_project_name[
341
+ selected_distribution.metadata.project_name
342
+ ]
343
+ if target.requirement_applies(constraint, extras=requirement.activated_extras)
344
+ ]
345
+ if not constraints:
346
+ return True
347
+
348
+ return all(
349
+ constraint.contains(selected_distribution, prereleases=allow_prereleases)
350
+ for constraint in constraints
351
+ )
352
+
353
+ def error(message):
354
+ # type: (str) -> Error
355
+ return Error(
356
+ "Resolve from venv at {venv} failed: {message}".format(
357
+ venv=venv.venv_dir, message=message
358
+ )
359
+ )
360
+
361
+ to_resolve = deque(
362
+ OrderedSet(ResolveRequirement(requirement) for requirement in requirements)
363
+ ) # type: Deque[ResolveRequirement]
364
+ resolved = set() # type: Set[ProjectName]
365
+ while to_resolve:
366
+ requirement = to_resolve.popleft()
367
+ if requirement.project_name in resolved:
368
+ continue
369
+
370
+ if not requirement.applies(target, dependency_configuration):
371
+ continue
372
+
373
+ distribution = find_distribution(requirement.project_name, search_path)
374
+ if not distribution:
375
+ yield error(
376
+ "The virtual environment does not have {project_name} installed but it is required "
377
+ "by {requirement}".format(
378
+ project_name=requirement.project_name, requirement=requirement
379
+ )
380
+ )
381
+ elif meets_requirement(distribution, requirement):
382
+ production_assert(distribution.type is DistributionType.INSTALLED)
383
+ resolved.add(requirement.project_name)
384
+ editable_project_url = distribution.editable_install_url()
385
+ if (
386
+ not editable_project_url
387
+ and distribution.metadata.files.metadata.type is MetadataType.DIST_INFO
388
+ ):
389
+ to_resolve.extend(
390
+ requirement.dependency(
391
+ requirement=dependency,
392
+ target=target,
393
+ dependency_configuration=dependency_configuration,
394
+ )
395
+ for dependency in distribution.metadata.requires_dists
396
+ )
397
+ yield distribution
398
+ else:
399
+ source_requirement = (
400
+ "{project} @ {url}".format(
401
+ project=distribution.metadata.project_name, url=editable_project_url
402
+ )
403
+ if editable_project_url
404
+ else str(distribution.as_requirement())
405
+ )
406
+ result = resolver.resolve_requirements(
407
+ requirements=[source_requirement],
408
+ targets=Targets.from_target(target),
409
+ transitive=False,
410
+ compile=compile,
411
+ ignore_errors=ignore_errors,
412
+ result_type=result_type,
413
+ )
414
+ for dist in result.distributions:
415
+ to_resolve.extend(
416
+ requirement.dependency(
417
+ requirement=dependency,
418
+ target=target,
419
+ dependency_configuration=dependency_configuration,
420
+ )
421
+ for dependency in dist.distribution.metadata.requires_dists
422
+ )
423
+ yield dist.fingerprinted_distribution
424
+ else:
425
+ yield error(
426
+ "The virtual environment has {project_name} {version} installed but it does not "
427
+ "meet {requirement}{suffix}.".format(
428
+ project_name=distribution.project_name,
429
+ version=distribution.version,
430
+ requirement=requirement,
431
+ suffix=(
432
+ " due to supplied constraints"
433
+ if requirement.contains(distribution, prereleases=allow_prereleases)
434
+ else ""
435
+ ),
436
+ )
437
+ )
438
+
439
+
440
+ @attr.s(frozen=True)
441
+ class VenvResolveResult(object):
442
+ venv = attr.ib() # type: Virtualenv
443
+ venv_distributions = attr.ib() # type: Tuple[Distribution, ...]
444
+ re_resolved_distributions = attr.ib() # type: Tuple[FingerprintedDistribution, ...]
445
+ direct_requirements_by_project_name = attr.ib(
446
+ eq=False
447
+ ) # type: Mapping[ProjectName, Iterable[Requirement]]
448
+
449
+ @property
450
+ def target(self):
451
+ # type: () -> LocalInterpreter
452
+ return LocalInterpreter.create(self.venv.interpreter)
453
+
454
+
455
+ def _resolve_from_venv(
456
+ venv, # type: Virtualenv
457
+ requirement_configuration, # type: RequirementConfiguration
458
+ pip_configuration, # type: PipConfiguration
459
+ compile, # type: bool
460
+ ignore_errors, # type: bool
461
+ result_type, # type: InstallableType.Value
462
+ dependency_configuration, # type: DependencyConfiguration
463
+ ):
464
+ # type: (...) -> Union[VenvResolveResult, Error]
465
+ target = LocalInterpreter.create(venv.interpreter)
466
+
467
+ if pip_configuration.version:
468
+ compatible_pip_version = (
469
+ pip_configuration.version
470
+ if pip_configuration.version.requires_python_applies(target)
471
+ else PipVersion.latest_compatible(target)
472
+ )
473
+ if pip_configuration.version != compatible_pip_version:
474
+ if pip_configuration.allow_version_fallback:
475
+ pex_warnings.warn(
476
+ "Adjusted Pip version from {version} to {compatible_version} to work with the "
477
+ "venv interpreter.".format(
478
+ version=pip_configuration.version, compatible_version=compatible_pip_version
479
+ )
480
+ )
481
+ else:
482
+ return Error(
483
+ "Pip version {version} is not compatible with the Python {python_version} "
484
+ "venv interpreter.".format(
485
+ version=pip_configuration.version, python_version=venv.interpreter.python
486
+ )
487
+ )
488
+ elif PipVersion.DEFAULT.requires_python_applies(target):
489
+ compatible_pip_version = PipVersion.DEFAULT
490
+ else:
491
+ compatible_pip_version = PipVersion.latest_compatible(target)
492
+
493
+ resolver = ConfiguredResolver(
494
+ pip_configuration=attr.evolve(pip_configuration, version=compatible_pip_version)
495
+ )
496
+ fingerprinted_distributions = [] # type: List[FingerprintedDistribution]
497
+ venv_distributions = [] # type: List[Distribution]
498
+ direct_requirements_by_project_name = defaultdict(
499
+ OrderedSet
500
+ ) # type: DefaultDict[ProjectName, OrderedSet[Requirement]]
501
+ if requirement_configuration.has_requirements:
502
+ parsed_requirements = requirement_configuration.parse_requirements(
503
+ network_configuration=pip_configuration.network_configuration
504
+ )
505
+ local_project_requirements = OrderedSet() # type: OrderedSet[LocalProjectRequirement]
506
+ root_requirements = OrderedSet() # type: OrderedSet[Requirement]
507
+ for parsed_requirement in parsed_requirements:
508
+ if isinstance(parsed_requirement, LocalProjectRequirement):
509
+ local_project_requirements.add(parsed_requirement)
510
+ else:
511
+ root_requirements.add(parsed_requirement.requirement)
512
+ direct_requirements_by_project_name[
513
+ parsed_requirement.requirement.project_name
514
+ ].add(parsed_requirement.requirement)
515
+
516
+ if local_project_requirements:
517
+ return Error(
518
+ "Local project directory requirements cannot be resolved from venvs.\n"
519
+ "Use the project name instead if it is installed in the venv."
520
+ )
521
+
522
+ constraints_by_project_name = defaultdict(
523
+ OrderedSet
524
+ ) # type: DefaultDict[ProjectName, OrderedSet[Constraint]]
525
+ for constraint in requirement_configuration.parse_constraints(
526
+ network_configuration=pip_configuration.network_configuration
527
+ ):
528
+ constraints_by_project_name[constraint.project_name].add(constraint.requirement)
529
+
530
+ for distribution_or_error in _resolve_distributions(
531
+ venv=venv,
532
+ resolver=resolver,
533
+ target=target,
534
+ search_path=tuple(
535
+ site_packages_dir.path for site_packages_dir in venv.interpreter.site_packages
536
+ ),
537
+ requirements=root_requirements,
538
+ constraints_by_project_name=constraints_by_project_name,
539
+ dependency_configuration=dependency_configuration,
540
+ allow_prereleases=pip_configuration.allow_prereleases,
541
+ compile=compile,
542
+ ignore_errors=ignore_errors,
543
+ result_type=result_type,
544
+ ):
545
+ if isinstance(distribution_or_error, Error):
546
+ return distribution_or_error
547
+ elif isinstance(distribution_or_error, FingerprintedDistribution):
548
+ fingerprinted_distributions.append(distribution_or_error)
549
+ else:
550
+ venv_distributions.append(distribution_or_error)
551
+ else:
552
+ sdists_to_resolve = []
553
+ for venv_distribution in venv.iter_distributions():
554
+ if venv_distribution.metadata.files.metadata.type is not MetadataType.DIST_INFO:
555
+ sdists_to_resolve.append(str(venv_distribution.as_requirement()))
556
+ else:
557
+ editable_project_url = venv_distribution.editable_install_url()
558
+ if editable_project_url:
559
+ sdists_to_resolve.append(
560
+ "{project_name} @ {url}".format(
561
+ project_name=venv_distribution.metadata.project_name,
562
+ url=editable_project_url,
563
+ )
564
+ )
565
+ else:
566
+ venv_distributions.append(venv_distribution)
567
+
568
+ direct_requirements_by_project_name[venv_distribution.metadata.project_name].add(
569
+ venv_distribution.as_requirement()
570
+ )
571
+ result = resolver.resolve_requirements(
572
+ sdists_to_resolve,
573
+ constraint_files=requirement_configuration.constraint_files,
574
+ targets=Targets.from_target(target),
575
+ transitive=False,
576
+ compile=compile,
577
+ ignore_errors=ignore_errors,
578
+ )
579
+ fingerprinted_distributions.extend(
580
+ dist.fingerprinted_distribution for dist in result.distributions
581
+ )
582
+
583
+ return VenvResolveResult(
584
+ venv=venv,
585
+ venv_distributions=tuple(venv_distributions),
586
+ re_resolved_distributions=tuple(fingerprinted_distributions),
587
+ direct_requirements_by_project_name=direct_requirements_by_project_name,
588
+ )
589
+
590
+
591
+ def resolve_from_venvs(
592
+ targets, # type: Targets
593
+ venvs, # type: Tuple[Virtualenv, ...]
594
+ requirement_configuration=RequirementConfiguration(), # type: RequirementConfiguration
595
+ pip_configuration=PipConfiguration(), # type: PipConfiguration
596
+ compile=False, # type: bool
597
+ ignore_errors=False, # type: bool
598
+ result_type=InstallableType.INSTALLED_WHEEL_CHROOT, # type: InstallableType.Value
599
+ dependency_configuration=DependencyConfiguration(), # type: DependencyConfiguration
600
+ ):
601
+ # type: (...) -> Union[ResolveResult, Error]
602
+
603
+ if not targets.is_empty:
604
+ return Error(
605
+ "You configured custom targets via --python, --interpreter-constraint, --platform or "
606
+ "--complete-platform but custom targets are not allowed when resolving from {venvs}.\n"
607
+ "For such resolves, the supported target is implicitly the one matching the venv "
608
+ "{interpreters}; in this case:{targets}.".format(
609
+ venvs="a virtual environment" if len(venvs) == 1 else "virtual environments",
610
+ interpreters=pluralize(venvs, "interpreter"),
611
+ targets=(
612
+ " {target}".format(
613
+ target=LocalInterpreter.create(venvs[0].interpreter).render_description()
614
+ )
615
+ if len(venvs) == 1
616
+ else "\n {targets}".format(
617
+ targets="\n ".join(
618
+ LocalInterpreter.create(venv.interpreter).render_description()
619
+ for venv in venvs
620
+ )
621
+ )
622
+ ),
623
+ )
624
+ )
625
+
626
+ errors = [] # type: List[Error]
627
+ venv_resolve_results = [] # type: List[VenvResolveResult]
628
+ for result in iter_map_parallel(
629
+ venvs,
630
+ functools.partial(
631
+ _resolve_from_venv,
632
+ requirement_configuration=requirement_configuration,
633
+ pip_configuration=pip_configuration,
634
+ compile=compile,
635
+ ignore_errors=ignore_errors,
636
+ result_type=result_type,
637
+ dependency_configuration=dependency_configuration,
638
+ ),
639
+ ):
640
+ if isinstance(result, Error):
641
+ errors.append(result)
642
+ else:
643
+ venv_resolve_results.append(result)
644
+
645
+ if len(errors) == 1:
646
+ return errors[0]
647
+ elif errors:
648
+ return Error(
649
+ "Failed to resolve from {count} of {total} virtual environments:\n{failures}".format(
650
+ count=len(errors),
651
+ total=len(venvs),
652
+ failures="\n".join(
653
+ "{index}. {error}".format(index=index, error=error)
654
+ for index, error in enumerate(errors, start=1)
655
+ ),
656
+ )
657
+ )
658
+
659
+ return ResolveResult(
660
+ dependency_configuration=dependency_configuration,
661
+ distributions=tuple(
662
+ _install_venv_distributions(
663
+ venv_resolve_results,
664
+ max_install_jobs=pip_configuration.max_jobs,
665
+ result_type=result_type,
666
+ use_system_time=True,
667
+ )
668
+ ),
669
+ type=result_type,
670
+ )