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/resolve/project.py CHANGED
@@ -3,35 +3,44 @@
3
3
 
4
4
  from __future__ import absolute_import
5
5
 
6
+ import hashlib
6
7
  import os.path
7
8
  from argparse import Namespace, _ActionsContainer
8
9
 
9
- from pex import requirements, toml
10
+ from pex import requirements, sdist, toml
10
11
  from pex.build_system import pep_517
11
- from pex.common import pluralize
12
+ from pex.common import pluralize, safe_mkdtemp
12
13
  from pex.compatibility import string
13
14
  from pex.dependency_configuration import DependencyConfiguration
14
- from pex.dist_metadata import DistMetadata, Requirement, RequirementParseError
15
+ from pex.dist_metadata import DistMetadata, Requirement, RequirementParseError, is_wheel
15
16
  from pex.fingerprinted_distribution import FingerprintedDistribution
16
17
  from pex.interpreter import PythonInterpreter
17
- from pex.jobs import Raise, SpawnedJob, execute_parallel
18
+ from pex.jobs import Job, Retain, SpawnedJob, execute_parallel
18
19
  from pex.orderedset import OrderedSet
19
20
  from pex.pep_427 import InstallableType
20
21
  from pex.pep_503 import ProjectName
22
+ from pex.pip.tool import PackageIndexConfiguration
21
23
  from pex.pip.version import PipVersionValue
22
- from pex.requirements import LocalProjectRequirement, ParseError
24
+ from pex.requirements import LocalProjectRequirement, ParseError, URLRequirement
23
25
  from pex.resolve.configured_resolve import resolve
26
+ from pex.resolve.configured_resolver import ConfiguredResolver
24
27
  from pex.resolve.requirement_configuration import RequirementConfiguration
25
28
  from pex.resolve.resolver_configuration import PipConfiguration
26
- from pex.resolve.resolvers import Resolver, Untranslatable
29
+ from pex.resolve.resolvers import Resolver
30
+ from pex.resolver import BuildAndInstallRequest, BuildRequest, InstallRequest
31
+ from pex.result import Error, ResultError
27
32
  from pex.sorted_tuple import SortedTuple
28
33
  from pex.targets import LocalInterpreter, Target, Targets
34
+ from pex.tracer import TRACER
29
35
  from pex.typing import TYPE_CHECKING
36
+ from pex.util import CacheHelper
30
37
 
31
38
  if TYPE_CHECKING:
32
- from typing import Any, Iterable, Iterator, List, Mapping, Optional, Set, Tuple, Union
39
+ from typing import Any, Dict, Iterable, Iterator, List, Mapping, Optional, Set, Tuple, Union
33
40
 
34
41
  import attr # vendor:skip
42
+
43
+ from pex.requirements import ParsedRequirement
35
44
  else:
36
45
  from pex.third_party import attr
37
46
 
@@ -69,10 +78,14 @@ class BuiltProject(object):
69
78
 
70
79
 
71
80
  @attr.s(frozen=True)
72
- class Project(object):
73
- path = attr.ib() # type: str
81
+ class ProjectDirectory(object):
74
82
  requirement = attr.ib() # type: LocalProjectRequirement
75
83
 
84
+ @property
85
+ def path(self):
86
+ # type: () -> str
87
+ return self.requirement.path
88
+
76
89
  @property
77
90
  def requirement_str(self):
78
91
  # type: () -> str
@@ -80,9 +93,30 @@ class Project(object):
80
93
  return str(self.requirement.line.processed_text)
81
94
 
82
95
 
96
+ @attr.s(frozen=True)
97
+ class ProjectArchive(object):
98
+ requirement = attr.ib() # type: URLRequirement
99
+
100
+ @property
101
+ def path(self):
102
+ # type: () -> str
103
+ return self.requirement.url.path
104
+
105
+ @property
106
+ def is_wheel(self):
107
+ # type: () -> bool
108
+ return is_wheel(self.path)
109
+
110
+ @property
111
+ def subdirectory(self):
112
+ # type: () -> Optional[str]
113
+ return self.requirement.subdirectory
114
+
115
+
83
116
  @attr.s(frozen=True)
84
117
  class Projects(object):
85
- projects = attr.ib(default=()) # type: Tuple[Project, ...]
118
+ project_directories = attr.ib(default=()) # type: Tuple[ProjectDirectory, ...]
119
+ project_archives = attr.ib(default=()) # type: Tuple[ProjectArchive, ...]
86
120
 
87
121
  def build(
88
122
  self,
@@ -95,24 +129,93 @@ class Projects(object):
95
129
  ):
96
130
  # type: (...) -> Iterator[BuiltProject]
97
131
 
98
- resolve_result = resolve(
99
- targets=targets,
100
- requirement_configuration=RequirementConfiguration(
101
- requirements=[project.requirement_str for project in self.projects]
102
- ),
103
- resolver_configuration=attr.evolve(pip_configuration, transitive=False),
104
- compile_pyc=compile_pyc,
105
- ignore_errors=ignore_errors,
106
- result_type=result_type,
107
- dependency_configuration=dependency_config,
108
- )
109
- for resolved_distribution in resolve_result.distributions:
110
- yield BuiltProject(
111
- target=resolved_distribution.target,
112
- fingerprinted_distribution=resolved_distribution.fingerprinted_distribution,
113
- satisfied_direct_requirements=resolved_distribution.direct_requirements,
132
+ if self.project_directories:
133
+ resolve_result = resolve(
134
+ targets=targets,
135
+ requirement_configuration=RequirementConfiguration(
136
+ requirements=[project.requirement_str for project in self.project_directories]
137
+ ),
138
+ resolver_configuration=attr.evolve(pip_configuration, transitive=False),
139
+ compile_pyc=compile_pyc,
140
+ ignore_errors=ignore_errors,
141
+ result_type=result_type,
142
+ dependency_configuration=dependency_config,
143
+ )
144
+ for resolved_distribution in resolve_result.distributions:
145
+ yield BuiltProject(
146
+ target=resolved_distribution.target,
147
+ fingerprinted_distribution=resolved_distribution.fingerprinted_distribution,
148
+ satisfied_direct_requirements=resolved_distribution.direct_requirements,
149
+ )
150
+
151
+ if self.project_archives:
152
+ build_requests = [] # type: List[BuildRequest]
153
+ install_requests = [] # type: List[InstallRequest]
154
+ direct_requirements = [] # type: List[ParsedRequirement]
155
+ for project_archive in self.project_archives:
156
+ fingerprint = CacheHelper.hash(project_archive.path, hasher=hashlib.sha256)
157
+ direct_requirements.append(project_archive.requirement)
158
+ for target in targets.unique_targets():
159
+ if project_archive.is_wheel:
160
+ install_requests.append(
161
+ InstallRequest(
162
+ download_target=target,
163
+ wheel_path=project_archive.path,
164
+ fingerprint=fingerprint,
165
+ )
166
+ )
167
+ else:
168
+ build_requests.append(
169
+ BuildRequest(
170
+ download_target=target,
171
+ source_path=project_archive.path,
172
+ fingerprint=fingerprint,
173
+ subdirectory=project_archive.subdirectory,
174
+ )
175
+ )
176
+
177
+ build_and_install_request = BuildAndInstallRequest(
178
+ build_requests=build_requests,
179
+ install_requests=install_requests,
180
+ direct_requirements=direct_requirements,
181
+ package_index_configuration=PackageIndexConfiguration.create(
182
+ pip_version=pip_configuration.version,
183
+ resolver_version=pip_configuration.resolver_version,
184
+ repos_configuration=pip_configuration.repos_configuration,
185
+ network_configuration=pip_configuration.network_configuration,
186
+ use_pip_config=pip_configuration.use_pip_config,
187
+ extra_pip_requirements=pip_configuration.extra_requirements,
188
+ keyring_provider=pip_configuration.keyring_provider,
189
+ ),
190
+ compile=compile_pyc,
191
+ build_configuration=pip_configuration.build_configuration,
192
+ pip_version=pip_configuration.version,
193
+ resolver=ConfiguredResolver(pip_configuration=pip_configuration),
194
+ dependency_configuration=dependency_config,
114
195
  )
115
196
 
197
+ # This checks the resolve, but we're not doing a full resolve here - we're installing
198
+ # projects to gather their requirements and _then_ perform a resolve of those
199
+ # requirements.
200
+ ignore_errors = True
201
+
202
+ if result_type is InstallableType.INSTALLED_WHEEL_CHROOT:
203
+ resolved_distributions = build_and_install_request.install_distributions(
204
+ max_parallel_jobs=pip_configuration.max_jobs, ignore_errors=ignore_errors
205
+ )
206
+ else:
207
+ resolved_distributions = build_and_install_request.build_distributions(
208
+ max_parallel_jobs=pip_configuration.max_jobs,
209
+ ignore_errors=ignore_errors,
210
+ )
211
+
212
+ for resolved_distribution in resolved_distributions:
213
+ yield BuiltProject(
214
+ target=resolved_distribution.target,
215
+ fingerprinted_distribution=resolved_distribution.fingerprinted_distribution,
216
+ satisfied_direct_requirements=resolved_distribution.direct_requirements,
217
+ )
218
+
116
219
  def collect_requirements(
117
220
  self,
118
221
  resolver, # type: Resolver
@@ -123,33 +226,105 @@ class Projects(object):
123
226
  # type: (...) -> Iterator[Requirement]
124
227
 
125
228
  target = LocalInterpreter.create(interpreter)
229
+ seen = set() # type: Set[Requirement]
230
+
231
+ source_projects = list(
232
+ self.project_directories
233
+ ) # type: List[Union[ProjectDirectory, ProjectArchive]]
234
+ for project_archive in self.project_archives:
235
+ if project_archive.is_wheel:
236
+ for req in DistMetadata.load(project_archive.path).requires_dists:
237
+ if req not in seen:
238
+ seen.add(req)
239
+ yield req
240
+ else:
241
+ source_projects.append(project_archive)
242
+
243
+ wheels_to_build = [] # type: List[str]
244
+ prepare_metadata_errors = {} # type: Dict[str, str]
245
+
246
+ def spawn_prepare_metadata_func(project):
247
+ # type: (Union[ProjectDirectory, ProjectArchive]) -> SpawnedJob[DistMetadata]
248
+
249
+ if isinstance(project, ProjectDirectory):
250
+ project_dir = project.path
251
+ else:
252
+ project_dir = sdist.extract_tarball(
253
+ tarball_path=project.path, dest_dir=safe_mkdtemp()
254
+ )
126
255
 
127
- def spawn_func(project):
128
- # type: (Project) -> SpawnedJob[DistMetadata]
129
256
  return pep_517.spawn_prepare_metadata(
130
- project.path, target, resolver, pip_version=pip_version
257
+ project_directory=project_dir,
258
+ target=target,
259
+ resolver=resolver,
260
+ pip_version=pip_version,
131
261
  )
132
262
 
133
- seen = set() # type: Set[Requirement]
134
- for local_project, dist_metadata in zip(
135
- self.projects,
263
+ for project_directory, dist_metadata_result in zip(
264
+ source_projects,
136
265
  execute_parallel(
137
- self.projects,
138
- spawn_func=spawn_func,
139
- error_handler=Raise[Project, DistMetadata](Untranslatable),
266
+ source_projects,
267
+ # MyPy just can't figure out the next two args types; they're OK.
268
+ spawn_func=spawn_prepare_metadata_func, # type: ignore[arg-type]
269
+ error_handler=Retain["Union[ProjectDirectory, ProjectArchive]"](), # type: ignore[arg-type]
140
270
  max_jobs=max_jobs,
141
271
  ),
142
272
  ):
143
- for req in _iter_requirements(
144
- target=target, dist_metadata=dist_metadata, extras=local_project.requirement.extras
145
- ):
146
- if req not in seen:
147
- seen.add(req)
148
- yield req
273
+ if isinstance(dist_metadata_result, DistMetadata):
274
+ for req in _iter_requirements(
275
+ target=target,
276
+ dist_metadata=dist_metadata_result,
277
+ extras=project_directory.requirement.extras,
278
+ ):
279
+ if req not in seen:
280
+ seen.add(req)
281
+ yield req
282
+ else:
283
+ _item, error = dist_metadata_result
284
+ if isinstance(error, Job.Error) and pep_517.is_hook_unavailable_error(error):
285
+ TRACER.log(
286
+ "Failed to prepare metadata for {project}, trying to build a wheel "
287
+ "instead: {err}".format(
288
+ project=project_directory.path, err=dist_metadata_result
289
+ ),
290
+ V=3,
291
+ )
292
+ wheels_to_build.append(project_directory.path)
293
+ else:
294
+ prepare_metadata_errors[project_directory.path] = str(error)
295
+
296
+ if wheels_to_build:
297
+ resolve_result = resolver.resolve_requirements(
298
+ requirements=wheels_to_build,
299
+ targets=Targets.from_target(target),
300
+ pip_version=pip_version,
301
+ )
302
+ for resolved_distribution in resolve_result.distributions:
303
+ for req in resolved_distribution.distribution.requires():
304
+ if req not in seen:
305
+ seen.add(req)
306
+ yield req
307
+
308
+ if prepare_metadata_errors:
309
+ raise ResultError(
310
+ Error(
311
+ "Encountered {count} {errors} collecting project requirements:\n"
312
+ "{error_items}".format(
313
+ count=len(prepare_metadata_errors),
314
+ errors=pluralize(prepare_metadata_errors, "error"),
315
+ error_items="\n".join(
316
+ "{index}. {path}: {error}".format(index=index, path=path, error=error)
317
+ for index, (path, error) in enumerate(
318
+ prepare_metadata_errors.items(), start=1
319
+ )
320
+ ),
321
+ )
322
+ )
323
+ )
149
324
 
150
325
  def __len__(self):
151
326
  # type: () -> int
152
- return len(self.projects)
327
+ return len(self.project_directories) + len(self.project_archives)
153
328
 
154
329
 
155
330
  @attr.s(frozen=True)
@@ -330,7 +505,8 @@ def register_options(
330
505
  def get_projects(options):
331
506
  # type: (Namespace) -> Projects
332
507
 
333
- projects = [] # type: List[Project]
508
+ project_directories = [] # type: List[ProjectDirectory]
509
+ project_archives = [] # type: List[ProjectArchive]
334
510
  errors = [] # type: List[str]
335
511
  for project in getattr(options, "projects", ()):
336
512
  try:
@@ -342,18 +518,26 @@ def get_projects(options):
342
518
  )
343
519
  )
344
520
  else:
345
- if isinstance(parsed, LocalProjectRequirement):
521
+ if isinstance(parsed, (LocalProjectRequirement, URLRequirement)):
346
522
  if parsed.marker:
347
523
  errors.append(
348
524
  "The --project {project} has a marker, which is not supported. "
349
525
  "Remove marker: ;{marker}".format(project=project, marker=parsed.marker)
350
526
  )
527
+ elif isinstance(parsed, LocalProjectRequirement):
528
+ project_directories.append(ProjectDirectory(requirement=parsed))
529
+ elif parsed.url.scheme != "file":
530
+ errors.append(
531
+ "The --project {project} URL must be a local file: URL.".format(
532
+ project=project
533
+ )
534
+ )
351
535
  else:
352
- projects.append(Project(path=parsed.path, requirement=parsed))
536
+ project_archives.append(ProjectArchive(requirement=parsed))
353
537
  else:
354
538
  errors.append(
355
539
  "The --project {project} does not appear to point to a directory containing a "
356
- "Python project.".format(project=project)
540
+ "Python project or a project archive (sdist or whl).".format(project=project)
357
541
  )
358
542
 
359
543
  if errors:
@@ -368,7 +552,9 @@ def get_projects(options):
368
552
  )
369
553
  )
370
554
 
371
- return Projects(projects=tuple(projects))
555
+ return Projects(
556
+ project_directories=tuple(project_directories), project_archives=tuple(project_archives)
557
+ )
372
558
 
373
559
 
374
560
  def get_group_requirements(options):
@@ -5,11 +5,19 @@ from __future__ import absolute_import
5
5
 
6
6
  from pex.fetcher import URLFetcher
7
7
  from pex.network_configuration import NetworkConfiguration
8
- from pex.requirements import Constraint, parse_requirement_file, parse_requirement_strings
8
+ from pex.requirements import (
9
+ Constraint,
10
+ LocalProjectRequirement,
11
+ PyPIRequirement,
12
+ URLRequirement,
13
+ VCSRequirement,
14
+ parse_requirement_file,
15
+ parse_requirement_strings,
16
+ )
9
17
  from pex.typing import TYPE_CHECKING
10
18
 
11
19
  if TYPE_CHECKING:
12
- from typing import Iterable, List, Optional
20
+ from typing import Iterable, List, Optional, Tuple
13
21
 
14
22
  import attr # vendor:skip
15
23
 
@@ -18,14 +26,21 @@ else:
18
26
  from pex.third_party import attr
19
27
 
20
28
 
29
+ def _as_str_tuple(items):
30
+ # type: (Optional[Iterable[str]]) -> Tuple[str, ...]
31
+ if not items:
32
+ return ()
33
+ return items if isinstance(items, tuple) else tuple(items)
34
+
35
+
21
36
  @attr.s(frozen=True)
22
37
  class RequirementConfiguration(object):
23
- requirements = attr.ib(default=None) # type: Optional[Iterable[str]]
24
- requirement_files = attr.ib(default=None) # type: Optional[Iterable[str]]
25
- constraint_files = attr.ib(default=None) # type: Optional[Iterable[str]]
38
+ requirements = attr.ib(default=(), converter=_as_str_tuple) # type: Tuple[str, ...]
39
+ requirement_files = attr.ib(default=(), converter=_as_str_tuple) # type: Tuple[str, ...]
40
+ constraint_files = attr.ib(default=(), converter=_as_str_tuple) # type: Tuple[str, ...]
26
41
 
27
42
  def parse_requirements(self, network_configuration=None):
28
- # type: (Optional[NetworkConfiguration]) -> Iterable[ParsedRequirement]
43
+ # type: (Optional[NetworkConfiguration]) -> Tuple[ParsedRequirement, ...]
29
44
  parsed_requirements = [] # type: List[ParsedRequirement]
30
45
  if self.requirements:
31
46
  parsed_requirements.extend(parse_requirement_strings(self.requirements))
@@ -37,12 +52,15 @@ class RequirementConfiguration(object):
37
52
  for requirement_or_constraint in parse_requirement_file(
38
53
  requirement_file, is_constraints=False, fetcher=fetcher
39
54
  )
40
- if not isinstance(requirement_or_constraint, Constraint)
55
+ if isinstance(
56
+ requirement_or_constraint,
57
+ (PyPIRequirement, URLRequirement, VCSRequirement, LocalProjectRequirement),
58
+ )
41
59
  )
42
- return parsed_requirements
60
+ return tuple(parsed_requirements)
43
61
 
44
62
  def parse_constraints(self, network_configuration=None):
45
- # type: (Optional[NetworkConfiguration]) -> Iterable[Constraint]
63
+ # type: (Optional[NetworkConfiguration]) -> Tuple[Constraint, ...]
46
64
  parsed_constraints = [] # type: List[Constraint]
47
65
  if self.constraint_files:
48
66
  fetcher = URLFetcher(network_configuration=network_configuration)
@@ -54,7 +72,7 @@ class RequirementConfiguration(object):
54
72
  )
55
73
  if isinstance(requirement_or_constraint, Constraint)
56
74
  )
57
- return parsed_constraints
75
+ return tuple(parsed_constraints)
58
76
 
59
77
  @property
60
78
  def has_requirements(self):
@@ -3,17 +3,16 @@
3
3
 
4
4
  from __future__ import absolute_import
5
5
 
6
- import itertools
7
-
8
6
  from pex import pex_warnings
9
- from pex.auth import PasswordEntry
10
7
  from pex.enum import Enum
11
8
  from pex.jobs import DEFAULT_MAX_JOBS
12
9
  from pex.network_configuration import NetworkConfiguration
13
10
  from pex.pep_440 import Version
14
11
  from pex.pep_503 import ProjectName
15
12
  from pex.pip.version import PipVersion, PipVersionValue
13
+ from pex.resolve.package_repository import ReposConfiguration
16
14
  from pex.typing import TYPE_CHECKING
15
+ from pex.venv.virtualenv import Virtualenv
17
16
 
18
17
  if TYPE_CHECKING:
19
18
  from typing import Callable, FrozenSet, Iterable, Optional, Tuple, Union
@@ -26,9 +25,6 @@ else:
26
25
  from pex.third_party import attr
27
26
 
28
27
 
29
- PYPI = "https://pypi.org/simple"
30
-
31
-
32
28
  class ResolverVersion(Enum["ResolverVersion.Value"]):
33
29
  class Value(Enum.Value):
34
30
  pass
@@ -60,32 +56,6 @@ class ResolverVersion(Enum["ResolverVersion.Value"]):
60
56
  ResolverVersion.seal()
61
57
 
62
58
 
63
- @attr.s(frozen=True)
64
- class ReposConfiguration(object):
65
- @classmethod
66
- def create(
67
- cls,
68
- indexes=(), # type: Iterable[str]
69
- find_links=(), # type: Iterable[str]
70
- ):
71
- # type: (...) -> ReposConfiguration
72
- password_entries = []
73
- for url in itertools.chain(indexes, find_links):
74
- password_entry = PasswordEntry.maybe_extract_from_url(url)
75
- if password_entry:
76
- password_entries.append(password_entry)
77
-
78
- return cls(
79
- indexes=tuple(indexes),
80
- find_links=tuple(find_links),
81
- password_entries=tuple(password_entries),
82
- )
83
-
84
- indexes = attr.ib(default=(PYPI,)) # type: Tuple[str, ...]
85
- find_links = attr.ib(default=()) # type: Tuple[str, ...]
86
- password_entries = attr.ib(default=()) # type: Tuple[PasswordEntry, ...]
87
-
88
-
89
59
  @attr.s(frozen=True)
90
60
  class BuildConfiguration(object):
91
61
  class Error(ValueError):
@@ -277,3 +247,19 @@ class PreResolvedConfiguration(object):
277
247
  def network_configuration(self):
278
248
  # type: () -> NetworkConfiguration
279
249
  return self.pip_configuration.network_configuration
250
+
251
+
252
+ @attr.s(frozen=True)
253
+ class VenvRepositoryConfiguration(object):
254
+ venvs = attr.ib() # type: Tuple[Virtualenv, ...]
255
+ pip_configuration = attr.ib() # type: PipConfiguration
256
+
257
+ @property
258
+ def repos_configuration(self):
259
+ # type: () -> ReposConfiguration
260
+ return self.pip_configuration.repos_configuration
261
+
262
+ @property
263
+ def network_configuration(self):
264
+ # type: () -> NetworkConfiguration
265
+ return self.pip_configuration.network_configuration