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
@@ -7,8 +7,10 @@ import json
7
7
 
8
8
  from pex import compatibility
9
9
  from pex.artifact_url import Fingerprint
10
+ from pex.dependency_configuration import Override
10
11
  from pex.dist_metadata import Requirement, RequirementParseError
11
12
  from pex.enum import Enum
13
+ from pex.interpreter_constraints import InterpreterConstraint
12
14
  from pex.pep_440 import Version
13
15
  from pex.pep_503 import ProjectName
14
16
  from pex.pip.version import PipVersion
@@ -16,18 +18,20 @@ from pex.resolve.locked_resolve import (
16
18
  Artifact,
17
19
  FileArtifact,
18
20
  LocalProjectArtifact,
21
+ LockConfiguration,
19
22
  LockedRequirement,
20
23
  LockedResolve,
21
24
  LockStyle,
22
- TargetSystem,
23
25
  VCSArtifact,
24
26
  )
25
27
  from pex.resolve.lockfile.model import Lockfile
26
28
  from pex.resolve.path_mappings import PathMappings
27
29
  from pex.resolve.resolved_requirement import Pin
28
30
  from pex.resolve.resolver_configuration import BuildConfiguration, PipConfiguration, ResolverVersion
31
+ from pex.resolve.target_system import TargetSystem
29
32
  from pex.sorted_tuple import SortedTuple
30
33
  from pex.third_party.packaging import tags
34
+ from pex.third_party.packaging.markers import InvalidMarker, Marker
31
35
  from pex.third_party.packaging.specifiers import InvalidSpecifier, SpecifierSet
32
36
  from pex.typing import TYPE_CHECKING, cast
33
37
 
@@ -184,6 +188,18 @@ def loads(
184
188
  "The requirement string at '{path}' is invalid: {err}".format(path=path, err=e)
185
189
  )
186
190
 
191
+ def parse_override(
192
+ raw_override, # type: str
193
+ path, # type: str
194
+ ):
195
+ # type: (...) -> Override
196
+ try:
197
+ return Override.parse(raw_override)
198
+ except Override.InvalidError as e:
199
+ raise ParseError(
200
+ "The override string at '{path}' is invalid: {err}".format(path=path, err=e)
201
+ )
202
+
187
203
  def parse_version_specifier(
188
204
  raw_version_specifier, # type: str
189
205
  path, # type: str
@@ -196,6 +212,28 @@ def loads(
196
212
  "The version specifier at '{path}' is invalid: {err}".format(path=path, err=e)
197
213
  )
198
214
 
215
+ def parse_interpreter_constraint(
216
+ value, # type: str
217
+ path, # type: str
218
+ ):
219
+ # type: (...) -> InterpreterConstraint
220
+ try:
221
+ return InterpreterConstraint.parse(value)
222
+ except ValueError as e:
223
+ raise ParseError(
224
+ "The interpreter constraint at '{path}' is invalid: {err}".format(path=path, err=e)
225
+ )
226
+
227
+ def parse_marker(
228
+ raw_marker, # type: str
229
+ path, # type: str
230
+ ):
231
+ # type: (...) -> Marker
232
+ try:
233
+ return Marker(raw_marker)
234
+ except InvalidMarker as e:
235
+ raise ParseError("The marker at '{path}' is invalid: {err}".format(path=path, err=e))
236
+
199
237
  required_path_mappings = get("path_mappings", dict, optional=True) or {}
200
238
  given_mappings = set(mapping.name for mapping in path_mappings.mappings)
201
239
  unspecified_paths = set(required_path_mappings) - given_mappings
@@ -204,16 +242,33 @@ def loads(
204
242
  required_path_mappings=required_path_mappings, unspecified_paths=unspecified_paths
205
243
  )
206
244
 
207
- target_systems = [
208
- parse_enum_value(
209
- enum_type=TargetSystem,
210
- value=target_system,
211
- path=".target_systems[{index}]".format(index=index),
212
- )
213
- for index, target_system in enumerate(get("target_systems", list, optional=True) or ())
214
- ]
215
-
245
+ style = get_enum_value(LockStyle, "style")
216
246
  elide_unused_requires_dist = get("elide_unused_requires_dist", bool, optional=True) or False
247
+ if style is LockStyle.UNIVERSAL:
248
+ lock_configuration = LockConfiguration.universal(
249
+ interpreter_constraints=[
250
+ parse_interpreter_constraint(
251
+ value=interpreter_constraint,
252
+ path=".requires_python[{index}]".format(index=index),
253
+ )
254
+ for index, interpreter_constraint in enumerate(get("requires_python", list))
255
+ ],
256
+ systems=[
257
+ parse_enum_value(
258
+ enum_type=TargetSystem,
259
+ value=target_system,
260
+ path=".target_systems[{index}]".format(index=index),
261
+ )
262
+ for index, target_system in enumerate(
263
+ get("target_systems", list, optional=True) or ()
264
+ )
265
+ ],
266
+ elide_unused_requires_dist=elide_unused_requires_dist,
267
+ )
268
+ else:
269
+ lock_configuration = LockConfiguration(
270
+ style=style, elide_unused_requires_dist=elide_unused_requires_dist
271
+ )
217
272
 
218
273
  only_wheels = [
219
274
  parse_project_name(project_name, path=".only_wheels[{index}]".format(index=index))
@@ -245,8 +300,8 @@ def loads(
245
300
  ]
246
301
 
247
302
  overridden = [
248
- parse_requirement(req, path=".overridden[{index}]".format(index=index))
249
- for index, req in enumerate(get("overridden", list, optional=True) or ())
303
+ parse_override(override, path=".overridden[{index}]".format(index=index))
304
+ for index, override in enumerate(get("overridden", list, optional=True) or ())
250
305
  ]
251
306
 
252
307
  def assemble_tag(
@@ -280,6 +335,12 @@ def loads(
280
335
  if platform_tag_components
281
336
  else None
282
337
  )
338
+ marker_str = get("marker", str, data=locked_resolve, path=lock_path, optional=True)
339
+ marker = (
340
+ parse_marker(marker_str, path='{lock_path}["marker"]'.format(lock_path=lock_path))
341
+ if marker_str
342
+ else None
343
+ )
283
344
  locked_reqs = []
284
345
  for req_index, req in enumerate(
285
346
  get("locked_requirements", list, data=locked_resolve, path=lock_path)
@@ -337,15 +398,16 @@ def loads(
337
398
  )
338
399
 
339
400
  locked_resolves.append(
340
- LockedResolve(locked_requirements=SortedTuple(locked_reqs), platform_tag=platform_tag)
401
+ LockedResolve(
402
+ locked_requirements=SortedTuple(locked_reqs),
403
+ platform_tag=platform_tag,
404
+ marker=marker,
405
+ )
341
406
  )
342
407
 
343
408
  return Lockfile.create(
344
409
  pex_version=get("pex_version"),
345
- style=get_enum_value(LockStyle, "style"),
346
- requires_python=get("requires_python", list),
347
- target_systems=target_systems,
348
- elide_unused_requires_dist=elide_unused_requires_dist,
410
+ lock_configuration=lock_configuration,
349
411
  pip_version=get_enum_value(
350
412
  PipVersion,
351
413
  "pip_version",
@@ -406,12 +468,22 @@ def as_json_data(
406
468
  data["editable"] = artifact.editable
407
469
  return data
408
470
 
471
+ requires_python = [] # type: List[str]
472
+ target_systems = [] # type: List[str]
473
+ if lockfile.configuration.universal_target:
474
+ universal_target = lockfile.configuration.universal_target
475
+ requires_python.extend(
476
+ str(interpreter_constraint)
477
+ for interpreter_constraint in universal_target.iter_interpreter_constraints()
478
+ )
479
+ target_systems.extend(str(target_system) for target_system in universal_target.systems)
480
+
409
481
  return {
410
482
  "pex_version": lockfile.pex_version,
411
- "style": str(lockfile.style),
412
- "requires_python": list(lockfile.requires_python),
413
- "target_systems": [str(target_system) for target_system in lockfile.target_systems],
414
- "elide_unused_requires_dist": lockfile.elide_unused_requires_dist,
483
+ "style": str(lockfile.configuration.style),
484
+ "requires_python": requires_python,
485
+ "target_systems": target_systems,
486
+ "elide_unused_requires_dist": lockfile.configuration.elide_unused_requires_dist,
415
487
  "pip_version": str(lockfile.pip_version),
416
488
  "resolver_version": str(lockfile.resolver_version),
417
489
  "requirements": [
@@ -432,13 +504,16 @@ def as_json_data(
432
504
  "overridden": [str(override) for override in lockfile.overridden],
433
505
  "locked_resolves": [
434
506
  {
435
- "platform_tag": [
436
- locked_resolve.platform_tag.interpreter,
437
- locked_resolve.platform_tag.abi,
438
- locked_resolve.platform_tag.platform,
439
- ]
440
- if locked_resolve.platform_tag
441
- else None,
507
+ "platform_tag": (
508
+ [
509
+ locked_resolve.platform_tag.interpreter,
510
+ locked_resolve.platform_tag.abi,
511
+ locked_resolve.platform_tag.platform,
512
+ ]
513
+ if locked_resolve.platform_tag
514
+ else None
515
+ ),
516
+ "marker": str(locked_resolve.marker) if locked_resolve.marker else None,
442
517
  "locked_requirements": [
443
518
  {
444
519
  "project_name": str(req.pin.project_name),
@@ -5,19 +5,13 @@ from __future__ import absolute_import, print_function
5
5
 
6
6
  import os
7
7
 
8
- from pex.dependency_configuration import DependencyConfiguration
8
+ from pex.dependency_configuration import DependencyConfiguration, Override
9
9
  from pex.dist_metadata import Constraint, Requirement
10
10
  from pex.orderedset import OrderedSet
11
11
  from pex.pep_503 import ProjectName
12
12
  from pex.pip.version import PipVersion, PipVersionValue
13
13
  from pex.requirements import LocalProjectRequirement
14
- from pex.resolve.locked_resolve import (
15
- LocalProjectArtifact,
16
- LockConfiguration,
17
- LockedResolve,
18
- LockStyle,
19
- TargetSystem,
20
- )
14
+ from pex.resolve.locked_resolve import LocalProjectArtifact, LockConfiguration, LockedResolve
21
15
  from pex.resolve.lockfile import requires_dist
22
16
  from pex.resolve.resolved_requirement import Pin
23
17
  from pex.resolve.resolver_configuration import BuildConfiguration, ResolverVersion
@@ -40,21 +34,18 @@ class Lockfile(object):
40
34
  def create(
41
35
  cls,
42
36
  pex_version, # type: str
43
- style, # type: LockStyle.Value
44
- requires_python, # type: Iterable[str]
45
- target_systems, # type: Iterable[TargetSystem.Value]
37
+ lock_configuration, # type: LockConfiguration
46
38
  requirements, # type: Iterable[Union[Requirement, ParsedRequirement]]
47
39
  constraints, # type: Iterable[Constraint]
48
40
  allow_prereleases, # type: bool
49
41
  build_configuration, # type: BuildConfiguration
50
42
  transitive, # type: bool
51
43
  excluded, # type: Iterable[Requirement]
52
- overridden, # type: Iterable[Requirement]
44
+ overridden, # type: Iterable[Override]
53
45
  locked_resolves, # type: Iterable[LockedResolve]
54
46
  source=None, # type: Optional[str]
55
47
  pip_version=None, # type: Optional[PipVersionValue]
56
48
  resolver_version=None, # type: Optional[ResolverVersion.Value]
57
- elide_unused_requires_dist=False, # type: bool
58
49
  ):
59
50
  # type: (...) -> Lockfile
60
51
 
@@ -96,14 +87,11 @@ class Lockfile(object):
96
87
  return req.requirement
97
88
 
98
89
  resolve_requirements = OrderedSet(extract_requirement(req) for req in requirements)
99
-
100
90
  pip_ver = pip_version or PipVersion.DEFAULT
91
+
101
92
  return cls(
102
93
  pex_version=pex_version,
103
- style=style,
104
- requires_python=SortedTuple(requires_python),
105
- target_systems=SortedTuple(target_systems),
106
- elide_unused_requires_dist=elide_unused_requires_dist,
94
+ configuration=lock_configuration,
107
95
  pip_version=pip_ver,
108
96
  resolver_version=resolver_version or ResolverVersion.default(pip_ver),
109
97
  requirements=SortedTuple(resolve_requirements, key=str),
@@ -125,10 +113,12 @@ class Lockfile(object):
125
113
  requires_dist.remove_unused_requires_dist(
126
114
  resolve_requirements,
127
115
  locked_resolve,
128
- requires_python=requires_python,
129
- target_systems=target_systems,
116
+ universal_target=lock_configuration.universal_target,
117
+ dependency_configuration=DependencyConfiguration.create(
118
+ excluded=excluded, overridden=overridden
119
+ ),
130
120
  )
131
- if elide_unused_requires_dist
121
+ if lock_configuration.elide_unused_requires_dist
132
122
  else locked_resolve
133
123
  )
134
124
  for locked_resolve in locked_resolves
@@ -138,10 +128,7 @@ class Lockfile(object):
138
128
  )
139
129
 
140
130
  pex_version = attr.ib() # type: str
141
- style = attr.ib() # type: LockStyle.Value
142
- requires_python = attr.ib() # type: SortedTuple[str]
143
- target_systems = attr.ib() # type: SortedTuple[TargetSystem.Value]
144
- elide_unused_requires_dist = attr.ib() # type: bool
131
+ configuration = attr.ib() # type: LockConfiguration
145
132
  pip_version = attr.ib() # type: PipVersionValue
146
133
  resolver_version = attr.ib() # type: ResolverVersion.Value
147
134
  requirements = attr.ib() # type: SortedTuple[Requirement]
@@ -157,20 +144,11 @@ class Lockfile(object):
157
144
  use_system_time = attr.ib() # type: bool
158
145
  transitive = attr.ib() # type: bool
159
146
  excluded = attr.ib() # type: SortedTuple[Requirement]
160
- overridden = attr.ib() # type: SortedTuple[Requirement]
147
+ overridden = attr.ib() # type: SortedTuple[Override]
161
148
  locked_resolves = attr.ib() # type: SortedTuple[LockedResolve]
162
149
  local_project_requirement_mapping = attr.ib(eq=False) # type: Mapping[str, Requirement]
163
150
  source = attr.ib(default=None, eq=False) # type: Optional[str]
164
151
 
165
- def lock_configuration(self):
166
- # type: () -> LockConfiguration
167
- return LockConfiguration(
168
- style=self.style,
169
- requires_python=self.requires_python,
170
- target_systems=self.target_systems,
171
- elide_unused_requires_dist=self.elide_unused_requires_dist,
172
- )
173
-
174
152
  def build_configuration(self):
175
153
  # type: () -> BuildConfiguration
176
154
  return BuildConfiguration.create(
@@ -14,6 +14,7 @@ from pex.compatibility import text, urlparse
14
14
  from pex.dependency_configuration import DependencyConfiguration
15
15
  from pex.dist_metadata import Constraint, Requirement
16
16
  from pex.exceptions import production_assert
17
+ from pex.interpreter_implementation import InterpreterImplementation
17
18
  from pex.network_configuration import NetworkConfiguration
18
19
  from pex.orderedset import OrderedSet
19
20
  from pex.pep_425 import CompatibilityTags, RankedTag
@@ -27,7 +28,6 @@ from pex.resolve.locked_resolve import (
27
28
  LockedRequirement,
28
29
  LockedResolve,
29
30
  Resolved,
30
- TargetSystem,
31
31
  UnFingerprintedLocalProjectArtifact,
32
32
  UnFingerprintedVCSArtifact,
33
33
  VCSArtifact,
@@ -38,6 +38,7 @@ from pex.resolve.lockfile.subset import Subset, SubsetResult
38
38
  from pex.resolve.requirement_configuration import RequirementConfiguration
39
39
  from pex.resolve.resolved_requirement import Pin
40
40
  from pex.resolve.resolver_configuration import BuildConfiguration
41
+ from pex.resolve.target_system import TargetSystem, UniversalTarget
41
42
  from pex.result import Error, ResultError, try_
42
43
  from pex.sorted_tuple import SortedTuple
43
44
  from pex.targets import Target, Targets
@@ -175,53 +176,105 @@ def _elide_extras(marker):
175
176
  return marker
176
177
 
177
178
 
178
- def _to_environment(system):
179
- # type: (TargetSystem.Value) -> str
179
+ def _implementation_marker(
180
+ implementation, # type: InterpreterImplementation.Value
181
+ extra_marker=None, # type: Optional[Marker]
182
+ ):
183
+ # type: (...) -> str
184
+ platform_python_implementation = "platform_python_implementation == '{implementation}'".format(
185
+ implementation=implementation
186
+ )
187
+ if not extra_marker:
188
+ return platform_python_implementation
189
+ return "{platform_python_implementation} and {extra_marker}".format(
190
+ platform_python_implementation=platform_python_implementation, extra_marker=extra_marker
191
+ )
192
+
193
+
194
+ def _to_environment(
195
+ system, # type: TargetSystem.Value
196
+ implementation=None, # type: Optional[InterpreterImplementation.Value]
197
+ extra_marker=None, # type: Optional[Marker]
198
+ ):
199
+ # type: (...) -> str
200
+
180
201
  if system is TargetSystem.LINUX:
181
- return "platform_system == 'Linux'"
202
+ platform_system = "platform_system == 'Linux'"
182
203
  elif system is TargetSystem.MAC:
183
- return "platform_system == 'Darwin'"
204
+ platform_system = "platform_system == 'Darwin'"
184
205
  else:
185
206
  production_assert(system is TargetSystem.WINDOWS)
186
- return "platform_system == 'Windows'"
207
+ platform_system = "platform_system == 'Windows'"
208
+
209
+ if not implementation:
210
+ if not extra_marker:
211
+ return platform_system
212
+ return "{platform_system} and {extra_marker}".format(
213
+ platform_system=platform_system, extra_marker=extra_marker
214
+ )
215
+
216
+ return "{platform_system} and {platform_python_implementation}".format(
217
+ platform_system=platform_system,
218
+ platform_python_implementation=_implementation_marker(
219
+ implementation, extra_marker=extra_marker
220
+ ),
221
+ )
187
222
 
188
223
 
189
224
  def convert(
190
225
  root_requirements, # type: Iterable[Requirement]
191
226
  locked_resolve, # type: LockedResolve
192
227
  output, # type: IO[bytes]
193
- requires_python=None, # type: Optional[str]
194
- target_systems=(), # type: Iterable[TargetSystem.Value]
228
+ universal_target=None, # type: Optional[UniversalTarget]
195
229
  subset=(), # type: Iterable[DownloadableArtifact]
196
230
  include_dependency_info=True, # type bool
197
231
  ):
198
232
  # type: (...) -> None
199
233
 
200
- locked_resolve = remove_unused_requires_dist(
201
- resolve_requirements=root_requirements,
202
- locked_resolve=locked_resolve,
203
- requires_python=[requires_python] if requires_python else [],
204
- target_systems=target_systems,
205
- )
206
-
207
234
  pylock = OrderedDict() # type: OrderedDict[str, Any]
208
235
  pylock["lock-version"] = "1.0" # https://peps.python.org/pep-0751/#lock-version
209
236
 
210
- if target_systems:
237
+ if universal_target:
211
238
  # https://peps.python.org/pep-0751/#environments
212
239
  #
213
- # TODO: We just stick to mapping `--target-system` into markers currently but this should
214
- # probably include the full marker needed to rule out invalid installs, like Python 2.7
215
- # attempting to install a lock with only Python 3 wheels.
216
- pylock["environments"] = sorted(_to_environment(system) for system in target_systems)
217
- if requires_python:
218
- # https://peps.python.org/pep-0751/#requires-python
219
- #
220
- # TODO: This is currently just the `--interpreter-constraint` for `--style universal` locks
221
- # but it should probably be further refined (or purely calculated for non universal locks)
222
- # from locked project requires-python values and even more narrowly by locked projects with
223
- # only wheel artifacts by the wheel tags.
224
- pylock["requires-python"] = requires_python
240
+ # TODO: We just stick to mapping `--target-system` and any `--interpreter-constraint` that
241
+ # have an implementation specified into markers currently but this should probably include
242
+ # the full marker needed to rule out invalid installs, like Python 2.7 attempting to
243
+ # install a lock with only Python 3 wheels.
244
+ if universal_target.systems:
245
+ pylock["environments"] = sorted(
246
+ _to_environment(
247
+ system,
248
+ implementation=universal_target.implementation,
249
+ extra_marker=locked_resolve.marker,
250
+ )
251
+ for system in universal_target.systems
252
+ )
253
+ elif universal_target.implementation:
254
+ pylock["environments"] = [
255
+ _implementation_marker(
256
+ universal_target.implementation, extra_marker=locked_resolve.marker
257
+ )
258
+ ]
259
+ elif locked_resolve.marker:
260
+ pylock["environments"] = [str(locked_resolve.marker)]
261
+
262
+ if universal_target.requires_python:
263
+ if len(universal_target.requires_python) > 1:
264
+ # TODO(John Sirois): Provide a better error message. We could guide on OR
265
+ # to and AND paired with != to remove disjoint portions of the range.
266
+ raise ValueError(
267
+ "Can only convert a lock file with a single interpreter constraint."
268
+ )
269
+ requires_python = universal_target.requires_python[0]
270
+
271
+ # https://peps.python.org/pep-0751/#requires-python
272
+ #
273
+ # TODO: This is currently just any `--interpreter-constraint` specifiers for
274
+ # `--style universal` locks but it should probably be further refined (or purely
275
+ # calculated for non universal locks) from locked project requires-python values and
276
+ # even more narrowly by locked projects with only wheel artifacts by the wheel tags.
277
+ pylock["requires-python"] = str(requires_python)
225
278
 
226
279
  # TODO: These 3 assume a `pyproject.toml` is the input source for the lock. It almost never is
227
280
  # for current Pex lock use cases. Figure out if there is anything better that can be done.
@@ -251,6 +304,11 @@ def convert(
251
304
  if req.url and isinstance(parse_requirement_string(str(req)), URLRequirement)
252
305
  } # type: Dict[ProjectName, Requirement]
253
306
 
307
+ locked_resolve = remove_unused_requires_dist(
308
+ resolve_requirements=root_requirements,
309
+ locked_resolve=locked_resolve,
310
+ universal_target=universal_target,
311
+ )
254
312
  dependants_by_project_name = defaultdict(
255
313
  OrderedSet
256
314
  ) # type: DefaultDict[ProjectName, OrderedSet[Tuple[ProjectName, Optional[Marker]]]]
@@ -508,11 +566,12 @@ class ParseContext(object):
508
566
  self,
509
567
  key, # type: str
510
568
  default=None, # type: Optional[str]
569
+ footer_message=None, # type: Optional[str]
511
570
  ):
512
571
  # type: (...) -> str
513
572
  # The cast is of Python 2. The return type will actually be `unicode` in that case, but it
514
573
  # doesn't matter since there will be no further runtime type checks above this call.
515
- return cast(str, self.get(key, text, default=default))
574
+ return cast(str, self.get(key, text, default=default, footer_message=footer_message))
516
575
 
517
576
  def get_array_of_strings(
518
577
  self,
@@ -593,14 +652,16 @@ class ParseContext(object):
593
652
  key, # type: str
594
653
  item_type, # type: Type[_T]
595
654
  default=None, # type: Optional[_T]
655
+ footer_message=None, # type: Optional[str]
596
656
  ):
597
657
  # type: (...) -> _T
598
658
  value = self.table.get(key, None)
599
659
  if value is None:
600
660
  if default is None:
601
- raise ResultError(
602
- self.error("A value for {key} is required.".format(key=self.subpath(key)))
603
- )
661
+ msg_lines = ["A value for {key} is required.".format(key=self.subpath(key))]
662
+ if footer_message:
663
+ msg_lines.append(footer_message)
664
+ raise ResultError(self.error(os.linesep.join(msg_lines)))
604
665
  return default
605
666
  if not isinstance(value, item_type):
606
667
  raise ResultError(
@@ -797,43 +858,11 @@ class PackageIndex(object):
797
858
  return self._indexed_packages_by_name.get(project_name)
798
859
 
799
860
 
800
- def spec_matches(
801
- spec, # type: Any
802
- package_data, # type: Any
803
- ):
804
- # type: (...) -> bool
805
-
806
- if isinstance(spec, dict) and isinstance(package_data, dict):
807
- for key, value in spec.items():
808
- if not spec_matches(value, package_data.get(key)):
809
- return False
810
- return True
811
-
812
- if isinstance(spec, list) and isinstance(package_data, list):
813
- # All instances of lists in packages are currently array of tables:
814
- # + dependencies
815
- # + wheels
816
- # + attestation-identities
817
- #
818
- # As such, we consider the list matches if any of its contained tables matches each of the
819
- # spec tables. This allows a dependency spec like so to match the torch cpu wheel in a lock
820
- # that also includes torch-2.7.0-cp311-none-macosx_11_0_arm64.whl (cuda 12.8):
821
- # {name = "torch", wheels = [{ name = "torch-2.7.0+xpu-cp39-cp39-win_amd64.whl" }]}
822
- for spec_item in spec:
823
- if not any(
824
- spec_matches(spec_item, package_data_item) for package_data_item in package_data
825
- ):
826
- return False
827
- return True
828
-
829
- # I have no clue why MyPy can't track this as bool.
830
- return cast(bool, spec == package_data)
831
-
832
-
833
861
  @attr.s
834
862
  class PackageParser(object):
835
863
  package_index = attr.ib() # type: PackageIndex
836
864
  source = attr.ib() # type: str
865
+ created_by = attr.ib() # type: str
837
866
 
838
867
  parsed_packages_by_index = attr.ib(factory=dict) # type: Dict[int, Package]
839
868
 
@@ -905,7 +934,22 @@ class PackageParser(object):
905
934
  "dependencies", default=[], diagnostic_key="name"
906
935
  )
907
936
  for dep_idx, dep_parse_context in enumerate(dep_parse_contexts):
908
- dep_name = ProjectName(dep_parse_context.get_string("name"))
937
+ dep_name = ProjectName(
938
+ dep_parse_context.get_string(
939
+ "name",
940
+ footer_message=os.linesep.join(
941
+ (
942
+ "Pex requires dependency tables specify at least a `name`.",
943
+ "The pylock.toml spec does not require this however.",
944
+ "To subset locks created by {creator}, either they will need to add "
945
+ "names for their dependencies or Pex will need to support more "
946
+ "sophisticated dependency parsing.".format(creator=self.created_by),
947
+ "For more information, see the comments starting here: "
948
+ "https://github.com/pex-tool/pex/issues/2885#issuecomment-3263138568",
949
+ )
950
+ ),
951
+ )
952
+ )
909
953
 
910
954
  package_deps = self.package_index.packages(dep_name)
911
955
  if not package_deps:
@@ -915,31 +959,9 @@ class PackageParser(object):
915
959
  project_name=project_name.raw, dep_name=dep_name.raw
916
960
  )
917
961
  )
918
-
919
- deps = [
920
- indexed_package
921
- for indexed_package in package_deps
922
- if spec_matches(dep_parse_context.table, indexed_package.package_data)
923
- ] # type: List[IndexedPackage]
924
- if not deps:
925
- return dep_parse_context.error(
926
- "No matching {dep_name} package could be found for {project_name} "
927
- "dependencies[{dep_idx}].".format(
928
- dep_name=dep_name.raw, project_name=project_name.raw, dep_idx=dep_idx
929
- )
930
- )
931
- elif len(deps) > 1:
932
- return dep_parse_context.error(
933
- "More than one package matches {project_name} dependencies[{dep_idx}]:\n"
934
- "{matches}".format(
935
- project_name=project_name.raw,
936
- dep_idx=dep_idx,
937
- matches="\n".join(
938
- "+ packages[{index}]".format(index=dep.index) for dep in deps
939
- ),
940
- )
941
- )
942
- dependencies.append(Dependency(index=deps[0].index, project_name=dep_name))
962
+ dependencies.extend(
963
+ Dependency(index=dep.index, project_name=dep_name) for dep in package_deps
964
+ )
943
965
 
944
966
  vcs_parse_context = parse_context.get_table("vcs", default={})
945
967
  directory_parse_context = parse_context.get_table("directory", default={})
@@ -1342,12 +1364,7 @@ class Pylock(object):
1342
1364
  except TomlDecodeError as e:
1343
1365
  return parse_context.error("Failed to parse TOML", e)
1344
1366
 
1345
- lock_version = Version(
1346
- parse_context.get_string(
1347
- "lock-version",
1348
- "Pex only supports lock version 1.0 and refuses to guess compatibility.",
1349
- )
1350
- )
1367
+ lock_version = Version(parse_context.get_string("lock-version"))
1351
1368
  if lock_version != Version("1.0"):
1352
1369
  return parse_context.error(
1353
1370
  "Found `lock-version` {version}, but Pex only supports version 1.0.".format(
@@ -1370,7 +1387,9 @@ class Pylock(object):
1370
1387
  "packages", default=[], diagnostic_key="name"
1371
1388
  )
1372
1389
  package_index = PackageIndex.create(packages_data)
1373
- package_parser = PackageParser(package_index=package_index, source=pylock_toml_path)
1390
+ package_parser = PackageParser(
1391
+ package_index=package_index, source=pylock_toml_path, created_by=created_by
1392
+ )
1374
1393
 
1375
1394
  local_project_requirement_mapping = {} # type: Dict[str, Requirement]
1376
1395
  packages = [] # type: List[Package]