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/cli/commands/lock.py CHANGED
@@ -32,11 +32,12 @@ from pex.dist_metadata import (
32
32
  )
33
33
  from pex.enum import Enum
34
34
  from pex.exceptions import production_assert
35
+ from pex.installed_wheel import InstalledWheel
35
36
  from pex.interpreter import PythonInterpreter
36
37
  from pex.orderedset import OrderedSet
37
38
  from pex.os import is_exe, safe_execv
38
- from pex.pep_376 import InstalledWheel, Record
39
- from pex.pep_427 import InstallableType
39
+ from pex.pep_376 import InstalledFile, Record
40
+ from pex.pep_427 import InstallableType, reinstall_venv
40
41
  from pex.pep_440 import Version
41
42
  from pex.pep_503 import ProjectName
42
43
  from pex.pip.version import PipVersionValue
@@ -52,7 +53,6 @@ from pex.resolve.locked_resolve import (
52
53
  LockedResolve,
53
54
  LockStyle,
54
55
  Resolved,
55
- TargetSystem,
56
56
  VCSArtifact,
57
57
  )
58
58
  from pex.resolve.lockfile import json_codec, pep_751, requires_dist
@@ -77,6 +77,7 @@ from pex.resolve.resolver_options import parse_lockfile
77
77
  from pex.resolve.resolvers import Resolver
78
78
  from pex.resolve.script_metadata import ScriptMetadataApplication, apply_script_metadata
79
79
  from pex.resolve.target_configuration import InterpreterConstraintsNotSatisfied, TargetConfiguration
80
+ from pex.resolve.target_system import TargetSystem, UniversalTarget
80
81
  from pex.result import Error, Ok, Result, try_
81
82
  from pex.sorted_tuple import SortedTuple
82
83
  from pex.targets import LocalInterpreter, Target, Targets
@@ -88,8 +89,10 @@ from pex.version import __version__
88
89
  if TYPE_CHECKING:
89
90
  from typing import (
90
91
  IO,
92
+ Deque,
91
93
  Dict,
92
94
  Iterable,
95
+ Iterator,
93
96
  List,
94
97
  Mapping,
95
98
  Optional,
@@ -300,7 +303,10 @@ class SyncTarget(object):
300
303
  if distribution.metadata.type is MetadataType.DIST_INFO:
301
304
  to_unlink.extend(
302
305
  os.path.realpath(os.path.join(distribution.location, installed_file.path))
303
- for installed_file in Record.read(distribution.iter_metadata_lines("RECORD"))
306
+ for installed_file in Record.read(
307
+ lines=distribution.iter_metadata_lines("RECORD")
308
+ )
309
+ if isinstance(installed_file, InstalledFile)
304
310
  )
305
311
  elif distribution.metadata.type is MetadataType.EGG_INFO:
306
312
  installed_files = distribution.metadata.files.metadata_file_rel_path(
@@ -354,8 +360,8 @@ class SyncTarget(object):
354
360
 
355
361
  if to_install:
356
362
  for distribution in to_install:
357
- for src, dst in InstalledWheel.load(distribution.location).reinstall_venv(
358
- self.venv
363
+ for src, dst in reinstall_venv(
364
+ installed_wheel=InstalledWheel.load(distribution.location), venv=self.venv
359
365
  ):
360
366
  TRACER.log("Installed {src} -> {dst}".format(src=src, dst=dst))
361
367
  for script in self.venv.rewrite_scripts():
@@ -418,7 +424,7 @@ class LockUpdateRequest(object):
418
424
  lock_file=self._lock_file_path,
419
425
  missing_platforms="\n".join(
420
426
  sorted(
421
- "+ {platform}".format(platform=locked_resolve.platform_tag)
427
+ "+ {platform}".format(platform=locked_resolve.target_platform)
422
428
  for locked_resolve in self._lock_updater.lock_file.locked_resolves
423
429
  )
424
430
  ),
@@ -481,6 +487,162 @@ class LockfileSubset(object):
481
487
  locked_resolves = attr.ib() # type: Sequence[LockedResolve]
482
488
 
483
489
 
490
+ @attr.s(frozen=True)
491
+ class RootRequirement(object):
492
+ project_name = attr.ib() # type: ProjectName
493
+ requirements = attr.ib() # type: Tuple[Requirement, ...]
494
+
495
+ def select(self, project_name_and_version):
496
+ # type: (ProjectNameAndVersion) -> Tuple[Requirement, ...]
497
+ return tuple(
498
+ requirement
499
+ for requirement in self.requirements
500
+ if project_name_and_version in requirement
501
+ )
502
+
503
+ def __str__(self):
504
+ # type: () -> str
505
+ return " or ".join("'{req}'".format(req=requirement) for requirement in self.requirements)
506
+
507
+
508
+ @attr.s(frozen=True)
509
+ class RootRequirements(object):
510
+ @classmethod
511
+ def create(cls, requirements):
512
+ # type: (Iterable[Requirement]) -> RootRequirements
513
+
514
+ requirements_by_project_name = (
515
+ OrderedDict()
516
+ ) # type: OrderedDict[ProjectName, OrderedSet[Requirement]]
517
+ for requirement in requirements:
518
+ requirements_by_project_name.setdefault(requirement.project_name, OrderedSet()).add(
519
+ requirement
520
+ )
521
+
522
+ return cls(
523
+ root_requirements=tuple(
524
+ RootRequirement(project_name=project_name, requirements=tuple(reqs))
525
+ for project_name, reqs in requirements_by_project_name.items()
526
+ )
527
+ )
528
+
529
+ _root_requirements = attr.ib() # type: Tuple[RootRequirement, ...]
530
+
531
+ def iter_requirements(self):
532
+ # type: () -> Iterator[Requirement]
533
+ for root_requirement in self._root_requirements:
534
+ for requirement in root_requirement.requirements:
535
+ yield requirement
536
+
537
+ def __iter__(self):
538
+ # type: () -> Iterator[RootRequirement]
539
+ return iter(self._root_requirements)
540
+
541
+
542
+ def subset_locked_resolve(
543
+ locked_resolve, # type: LockedResolve
544
+ root_requirements, # type: RootRequirements
545
+ constraint_by_project_name, # type: Mapping[ProjectName, Constraint]
546
+ lock_file_description, # type: str
547
+ universal_target=None, # type: Optional[UniversalTarget]
548
+ ):
549
+ # type: (...) -> Union[LockedResolve, Error]
550
+
551
+ available = {
552
+ locked_req.pin.project_name: (
553
+ ProjectNameAndVersion(locked_req.pin.project_name.raw, locked_req.pin.version.raw),
554
+ locked_req,
555
+ )
556
+ for locked_req in locked_resolve.locked_requirements
557
+ }
558
+ retain = set()
559
+ to_resolve = deque(root_requirements) # type: Deque[Union[RootRequirement, Requirement]]
560
+ while to_resolve:
561
+ req = to_resolve.popleft()
562
+ if req.project_name in retain:
563
+ continue
564
+ retain.add(req.project_name)
565
+
566
+ dep = available.get(req.project_name)
567
+ if not dep:
568
+ return Error(
569
+ "There is no lock entry for {project} in {lock_file} to satisfy the "
570
+ "{transitive}{req} requirement.".format(
571
+ project=req.project_name,
572
+ lock_file=lock_file_description,
573
+ transitive="" if isinstance(req, RootRequirement) else "transitive ",
574
+ req=(req if isinstance(req, RootRequirement) else "'{req}'".format(req=req)),
575
+ )
576
+ )
577
+
578
+ pnav, locked_req = dep
579
+ if isinstance(req, RootRequirement):
580
+ reqs = req.select(pnav)
581
+ if not reqs:
582
+ return Error(
583
+ "The locked version of {project} in {lock_file} is {version} which "
584
+ "does not satisfy the {req} requirement.".format(
585
+ project=pnav.project_name,
586
+ lock_file=lock_file_description,
587
+ version=pnav.version,
588
+ req=req,
589
+ )
590
+ )
591
+ elif pnav not in req:
592
+ production_assert(
593
+ isinstance(req, RootRequirement),
594
+ "Transitive requirements in a lock should always match existing lock "
595
+ "entries. Found {project} {version} in {lock_file}, which does not satisfy "
596
+ "transitive requirement '{req}' found in the same lock.",
597
+ project=pnav.project_name,
598
+ version=pnav.version,
599
+ lock_file=lock_file_description,
600
+ req=req,
601
+ )
602
+ return Error(
603
+ "The locked version of {project} in {lock_file} is {version} which does "
604
+ "not satisfy the '{req}' requirement.".format(
605
+ project=pnav.project_name,
606
+ lock_file=lock_file_description,
607
+ version=pnav.version,
608
+ req=req,
609
+ )
610
+ )
611
+ elif (
612
+ req.project_name in constraint_by_project_name
613
+ and pnav not in constraint_by_project_name[req.project_name]
614
+ ):
615
+ return Error(
616
+ "The locked version of {project} in {lock_file} is {version} which does "
617
+ "not satisfy the '{constraint}' constraint.".format(
618
+ project=pnav.project_name,
619
+ lock_file=lock_file_description,
620
+ version=pnav.version,
621
+ constraint=constraint_by_project_name[req.project_name],
622
+ )
623
+ )
624
+ else:
625
+ reqs = (req,)
626
+
627
+ for req in reqs:
628
+ to_resolve.extend(
629
+ requires_dist.filter_dependencies(
630
+ req,
631
+ locked_req,
632
+ universal_target=universal_target,
633
+ )
634
+ )
635
+
636
+ return attr.evolve(
637
+ locked_resolve,
638
+ locked_requirements=SortedTuple(
639
+ locked_requirement
640
+ for locked_requirement in locked_resolve.locked_requirements
641
+ if locked_requirement.pin.project_name in retain
642
+ ),
643
+ )
644
+
645
+
484
646
  class Lock(OutputMixin, JsonMixin, BuildTimeCommand):
485
647
  """Operate on PEX lock files."""
486
648
 
@@ -527,7 +689,8 @@ class Lock(OutputMixin, JsonMixin, BuildTimeCommand):
527
689
  options_group,
528
690
  project_help=(
529
691
  "Add the transitive dependencies of the local project at the specified path to "
530
- "the lock but do not lock project itself."
692
+ "the lock but do not lock project itself. The path can be that of a project "
693
+ "directory, a project sdist or a pre-built project wheel."
531
694
  ),
532
695
  )
533
696
  dependency_configuration.register(options_group)
@@ -565,6 +728,18 @@ class Lock(OutputMixin, JsonMixin, BuildTimeCommand):
565
728
  def _add_lock_options(cls, parser):
566
729
  # type: (_ActionsContainer) -> None
567
730
  resolver_options.register_pex_lock_options(parser)
731
+ parser.add_argument(
732
+ "--avoid-downloads",
733
+ "--no-avoid-downloads",
734
+ dest="avoid_downloads",
735
+ default=True,
736
+ action=HandleBoolAction,
737
+ help=(
738
+ "When locking, prefer not downloading distributions unless necessary. This can "
739
+ "save time locking, although the downloads will need to happen later when using "
740
+ "the lock."
741
+ ),
742
+ )
568
743
 
569
744
  @classmethod
570
745
  def _add_create_arguments(cls, create_parser):
@@ -783,22 +958,6 @@ class Lock(OutputMixin, JsonMixin, BuildTimeCommand):
783
958
  ),
784
959
  )
785
960
 
786
- update_parser.add_argument(
787
- "--fingerprint-mismatch",
788
- default=FingerprintMismatch.ERROR,
789
- choices=FingerprintMismatch.values(),
790
- type=FingerprintMismatch.for_value,
791
- help=(
792
- "What to do when a lock update would result in at least one artifact fingerprint "
793
- "changing: {ignore!r} the mismatch and use the new fingerprint, {warn!r} about the "
794
- "mismatch but use the new fingerprint anyway or {error!r} and refuse to use the "
795
- "new mismatching fingerprint".format(
796
- ignore=FingerprintMismatch.IGNORE,
797
- warn=FingerprintMismatch.WARN,
798
- error=FingerprintMismatch.ERROR,
799
- )
800
- ),
801
- )
802
961
  cls._add_lockfile_option(update_parser, verb="update")
803
962
  cls._add_lock_options(update_parser)
804
963
  cls.add_json_options(update_parser, entity="lock", include_switch=False)
@@ -845,6 +1004,22 @@ class Lock(OutputMixin, JsonMixin, BuildTimeCommand):
845
1004
  )
846
1005
  ),
847
1006
  )
1007
+ update_parser.add_argument(
1008
+ "--fingerprint-mismatch",
1009
+ default=FingerprintMismatch.ERROR,
1010
+ choices=FingerprintMismatch.values(),
1011
+ type=FingerprintMismatch.for_value,
1012
+ help=(
1013
+ "What to do when a lock update would result in at least one artifact fingerprint "
1014
+ "changing: {ignore!r} the mismatch and use the new fingerprint, {warn!r} about the "
1015
+ "mismatch but use the new fingerprint anyway or {error!r} and refuse to use the "
1016
+ "new mismatching fingerprint".format(
1017
+ ignore=FingerprintMismatch.IGNORE,
1018
+ warn=FingerprintMismatch.WARN,
1019
+ error=FingerprintMismatch.ERROR,
1020
+ )
1021
+ ),
1022
+ )
848
1023
 
849
1024
  @classmethod
850
1025
  def _add_sync_arguments(cls, sync_parser):
@@ -1036,13 +1211,9 @@ class Lock(OutputMixin, JsonMixin, BuildTimeCommand):
1036
1211
  requirement_configuration = script_metadata_application.requirement_configuration
1037
1212
  target_configuration = script_metadata_application.target_configuration
1038
1213
  if self.options.style == LockStyle.UNIVERSAL:
1039
- lock_configuration = LockConfiguration(
1040
- style=LockStyle.UNIVERSAL,
1041
- requires_python=tuple(
1042
- str(interpreter_constraint.requires_python)
1043
- for interpreter_constraint in target_configuration.interpreter_constraints
1044
- ),
1045
- target_systems=tuple(self.options.target_systems),
1214
+ lock_configuration = LockConfiguration.universal(
1215
+ interpreter_constraints=target_configuration.interpreter_constraints,
1216
+ systems=self.options.target_systems,
1046
1217
  elide_unused_requires_dist=self.options.elide_unused_requires_dist,
1047
1218
  )
1048
1219
  elif self.options.target_systems:
@@ -1097,6 +1268,7 @@ class Lock(OutputMixin, JsonMixin, BuildTimeCommand):
1097
1268
  targets=targets,
1098
1269
  pip_configuration=pip_configuration,
1099
1270
  dependency_configuration=dependency_config,
1271
+ avoid_downloads=self.options.avoid_downloads,
1100
1272
  )
1101
1273
  )
1102
1274
  )
@@ -1158,56 +1330,74 @@ class Lock(OutputMixin, JsonMixin, BuildTimeCommand):
1158
1330
  # type: (RequirementConfiguration) -> Result
1159
1331
 
1160
1332
  lockfile_path, lock_file = self._load_lockfile()
1161
- if self.options.format is ExportFormat.PEP_751 and lock_file.style is LockStyle.UNIVERSAL:
1162
- self._check_pylock_toml_output_name()
1163
- production_assert(
1164
- len(lock_file.locked_resolves) == 1,
1165
- "A --style universal lock should have just one locked resolve, found {count}.",
1166
- count=len(lock_file.locked_resolves),
1333
+
1334
+ pip_configuration = resolver_options.create_pip_configuration(
1335
+ self.options, use_system_time=False
1336
+ )
1337
+ targets = target_options.configure(
1338
+ self.options, pip_configuration=pip_configuration
1339
+ ).resolve_targets()
1340
+ target = try_(
1341
+ targets.require_unique_target(
1342
+ purpose="exporting a lock in the {format!r} format".format(
1343
+ format=self.options.format
1344
+ )
1167
1345
  )
1346
+ )
1347
+
1348
+ if self.options.format is ExportFormat.PEP_751 and lock_file.configuration.universal_target:
1349
+ self._check_pylock_toml_output_name()
1168
1350
 
1169
- if requirement_configuration.has_requirements:
1351
+ if len(lock_file.locked_resolves) > 1 or requirement_configuration.has_requirements:
1170
1352
  lock_subset = try_(
1171
1353
  self._create_subset(
1172
1354
  lockfile_path,
1173
1355
  lock_file,
1174
- requirement_configuration=requirement_configuration,
1356
+ requirement_configuration=(
1357
+ requirement_configuration
1358
+ if requirement_configuration.has_requirements
1359
+ else RequirementConfiguration(
1360
+ requirements=map(str, lock_file.requirements)
1361
+ )
1362
+ ),
1175
1363
  )
1176
1364
  )
1365
+ marker_environment = target.marker_environment.as_dict()
1366
+ locked_resolves = [
1367
+ locked_resolve
1368
+ for locked_resolve in lock_subset.locked_resolves
1369
+ if not locked_resolve.marker
1370
+ or locked_resolve.marker.evaluate(marker_environment)
1371
+ ]
1372
+ count = len(locked_resolves)
1373
+ production_assert(
1374
+ count == 1,
1375
+ msg=(
1376
+ "Expected {target} to select one subset from {lock}, but found {count} "
1377
+ "locked resolve that applies."
1378
+ ),
1379
+ target=target.render_description(),
1380
+ lock=lockfile_path,
1381
+ count=count,
1382
+ )
1383
+ lock_subset = attr.evolve(lock_subset, locked_resolves=[locked_resolves[0]])
1177
1384
  else:
1178
1385
  lock_subset = LockfileSubset(
1179
1386
  root_requirements=lock_file.requirements,
1180
1387
  constraints=lock_file.constraints,
1181
- locked_resolves=lock_file.locked_resolves,
1388
+ locked_resolves=[lock_file.locked_resolves[0]],
1182
1389
  )
1183
1390
 
1184
- if len(lock_file.requires_python) > 1:
1185
- # TODO(John Sirois): Provide a better error message. We could guide on OR
1186
- # to and AND paired with != to remove disjoint portions of the range.
1187
- raise ValueError(
1188
- "Can only export a lock file with a single interpreter constraint."
1189
- )
1190
1391
  with self.output(self.options, binary=True) as toml_output:
1191
1392
  pep_751.convert(
1192
1393
  root_requirements=lock_subset.root_requirements,
1193
1394
  locked_resolve=lock_subset.locked_resolves[0],
1194
- requires_python=lock_file.requires_python[0],
1195
- target_systems=lock_file.target_systems,
1395
+ universal_target=lock_file.configuration.universal_target,
1196
1396
  output=toml_output,
1197
1397
  include_dependency_info=self.options.include_dependency_info,
1198
1398
  )
1199
1399
  return Ok()
1200
1400
 
1201
- pip_configuration = resolver_options.create_pip_configuration(
1202
- self.options, use_system_time=False
1203
- )
1204
- targets = target_options.configure(
1205
- self.options, pip_configuration=pip_configuration
1206
- ).resolve_targets()
1207
- target = targets.require_unique_target(
1208
- purpose="exporting a lock in the {format!r} format".format(format=self.options.format)
1209
- )
1210
-
1211
1401
  with TRACER.timed("Selecting locks for {target}".format(target=target)):
1212
1402
  subset_result = try_(
1213
1403
  subset(
@@ -1234,7 +1424,7 @@ class Lock(OutputMixin, JsonMixin, BuildTimeCommand):
1234
1424
  lockfile=lock_file.source,
1235
1425
  pip=ExportFormat.PIP,
1236
1426
  target=target,
1237
- platform=resolved.source.platform_tag,
1427
+ platform=resolved.source.target_platform,
1238
1428
  )
1239
1429
  )
1240
1430
  else:
@@ -1461,13 +1651,13 @@ class Lock(OutputMixin, JsonMixin, BuildTimeCommand):
1461
1651
  locking_configuration = LockingConfiguration(
1462
1652
  requirement_configuration,
1463
1653
  target_configuration=target_configuration,
1464
- lock_configuration=lock_file.lock_configuration(),
1654
+ lock_configuration=lock_file.configuration,
1465
1655
  script_metadata_application=script_metadata_application,
1466
1656
  )
1467
1657
  targets = try_(
1468
1658
  self._resolve_targets(
1469
1659
  action="creating",
1470
- style=lock_file.style,
1660
+ style=lock_file.configuration.style,
1471
1661
  target_configuration=locking_configuration.target_configuration,
1472
1662
  )
1473
1663
  )
@@ -1502,14 +1692,14 @@ class Lock(OutputMixin, JsonMixin, BuildTimeCommand):
1502
1692
  ],
1503
1693
  )
1504
1694
  )
1505
- root_requirements = {
1695
+ root_requirements = RootRequirements.create(
1506
1696
  (
1507
1697
  local_project_requirements[req]
1508
1698
  if isinstance(req, LocalProjectRequirement)
1509
1699
  else req.requirement
1510
1700
  )
1511
1701
  for req in parsed_requirements
1512
- }
1702
+ )
1513
1703
 
1514
1704
  constraint_by_project_name = OrderedDict(
1515
1705
  (constraint.requirement.project_name, constraint.requirement.as_constraint())
@@ -1519,91 +1709,41 @@ class Lock(OutputMixin, JsonMixin, BuildTimeCommand):
1519
1709
  )
1520
1710
 
1521
1711
  resolve_subsets = [] # type: List[LockedResolve]
1522
- for locked_resolve in lock_file.locked_resolves:
1523
- available = {
1524
- locked_req.pin.project_name: (
1525
- ProjectNameAndVersion(
1526
- locked_req.pin.project_name.raw, locked_req.pin.version.raw
1527
- ),
1528
- locked_req,
1712
+ errors = [] # type: List[Error]
1713
+ for index, locked_resolve in enumerate(lock_file.locked_resolves, start=0):
1714
+ if len(lock_file.locked_resolves) == 1:
1715
+ lock_file_description = lockfile_path
1716
+ else:
1717
+ lock_file_description = "locked resolve {index} (0-based) of {lock_file}".format(
1718
+ index=index, lock_file=lockfile_path
1529
1719
  )
1530
- for locked_req in locked_resolve.locked_requirements
1531
- }
1532
- retain = set()
1533
- to_resolve = deque(root_requirements)
1534
- while to_resolve:
1535
- req = to_resolve.popleft()
1536
- if req.project_name in retain:
1537
- continue
1538
- retain.add(req.project_name)
1539
- dep = available.get(req.project_name)
1540
- if not dep:
1541
- return Error(
1542
- "There is no lock entry for {project} in {lock_file} to satisfy the "
1543
- "{transitive}'{req}' requirement.".format(
1544
- project=req.project_name,
1545
- lock_file=lockfile_path,
1546
- transitive="" if req in root_requirements else "transitive ",
1547
- req=req,
1548
- )
1549
- )
1550
- elif dep:
1551
- pnav, locked_req = dep
1552
- if pnav not in req:
1553
- production_assert(
1554
- req in root_requirements,
1555
- "Transitive requirements in a lock should always match existing lock "
1556
- "entries. Found {project} {version} in {lock_file}, which does not "
1557
- "satisfy transitive requirement '{req}' found in the same lock.",
1558
- project=pnav.project_name,
1559
- version=pnav.version,
1560
- lock_file=lockfile_path,
1561
- req=req,
1562
- )
1563
- return Error(
1564
- "The locked version of {project} in {lock_file} is {version} which "
1565
- "does not satisfy the '{req}' requirement.".format(
1566
- project=pnav.project_name,
1567
- lock_file=lockfile_path,
1568
- version=pnav.version,
1569
- req=req,
1570
- )
1571
- )
1572
- elif (
1573
- req.project_name in constraint_by_project_name
1574
- and pnav not in constraint_by_project_name[req.project_name]
1575
- ):
1576
- return Error(
1577
- "The locked version of {project} in {lock_file} is {version} which "
1578
- "does not satisfy the '{constraint}' constraint.".format(
1579
- project=pnav.project_name,
1580
- lock_file=lockfile_path,
1581
- version=pnav.version,
1582
- constraint=constraint_by_project_name[req.project_name],
1583
- )
1584
- )
1585
- to_resolve.extend(
1586
- requires_dist.filter_dependencies(
1587
- req,
1588
- locked_req,
1589
- requires_python=lock_file.requires_python,
1590
- target_systems=lock_file.target_systems,
1591
- )
1592
- )
1593
1720
 
1594
- resolve_subsets.append(
1595
- attr.evolve(
1596
- locked_resolve,
1597
- locked_requirements=SortedTuple(
1598
- locked_requirement
1599
- for locked_requirement in locked_resolve.locked_requirements
1600
- if locked_requirement.pin.project_name in retain
1601
- ),
1721
+ result = subset_locked_resolve(
1722
+ locked_resolve,
1723
+ root_requirements,
1724
+ constraint_by_project_name,
1725
+ lock_file_description=lock_file_description,
1726
+ universal_target=lock_file.configuration.universal_target,
1727
+ )
1728
+ if isinstance(result, LockedResolve):
1729
+ resolve_subsets.append(result)
1730
+ else:
1731
+ errors.append(result)
1732
+
1733
+ if not resolve_subsets:
1734
+ if len(errors) == 1:
1735
+ return errors[0]
1736
+ return Error(
1737
+ "Failed to subset any of the {count} locked resolves contained in {lock_file}:\n"
1738
+ "{errors}".format(
1739
+ count=len(lock_file.locked_resolves),
1740
+ lock_file=lockfile_path,
1741
+ errors="\n".join(str(error) for error in errors),
1602
1742
  )
1603
1743
  )
1604
1744
 
1605
1745
  return LockfileSubset(
1606
- root_requirements=root_requirements,
1746
+ root_requirements=tuple(root_requirements.iter_requirements()),
1607
1747
  constraints=constraint_by_project_name.values(),
1608
1748
  locked_resolves=resolve_subsets,
1609
1749
  )
@@ -1651,6 +1791,7 @@ class Lock(OutputMixin, JsonMixin, BuildTimeCommand):
1651
1791
  use_pip_config=pip_configuration.use_pip_config,
1652
1792
  dependency_configuration=dependency_config,
1653
1793
  pip_log=resolver_options.get_pip_log(self.options),
1794
+ avoid_downloads=self.options.avoid_downloads,
1654
1795
  )
1655
1796
 
1656
1797
  target_configuration = target_options.configure(
@@ -1658,7 +1799,9 @@ class Lock(OutputMixin, JsonMixin, BuildTimeCommand):
1658
1799
  )
1659
1800
  targets = try_(
1660
1801
  self._resolve_targets(
1661
- action="updating", style=lock_file.style, target_configuration=target_configuration
1802
+ action="updating",
1803
+ style=lock_file.configuration.style,
1804
+ target_configuration=target_configuration,
1662
1805
  )
1663
1806
  )
1664
1807
 
@@ -1675,23 +1818,17 @@ class Lock(OutputMixin, JsonMixin, BuildTimeCommand):
1675
1818
  )
1676
1819
 
1677
1820
  with TRACER.timed("Selecting locks to update"):
1821
+ update_targets = (
1822
+ Targets.from_target(LocalInterpreter.create(targets.interpreter))
1823
+ if lock_file.configuration.style is LockStyle.UNIVERSAL
1824
+ else targets
1825
+ )
1678
1826
  locked_resolve_count = len(lock_file.locked_resolves)
1679
- if lock_file.style is LockStyle.UNIVERSAL and locked_resolve_count != 1:
1680
- return Error(
1681
- "The lock at {path} contains {count} locked resolves; so it "
1682
- "cannot be updated as a universal lock which requires exactly one locked "
1683
- "resolve.".format(path=lock_file_path, count=locked_resolve_count)
1684
- )
1685
1827
  if locked_resolve_count == 1:
1686
1828
  locked_resolve = lock_file.locked_resolves[0]
1687
- update_targets = (
1688
- [LocalInterpreter.create(targets.interpreter)]
1689
- if lock_file.style is LockStyle.UNIVERSAL
1690
- else targets.unique_targets()
1691
- )
1692
1829
  update_requests = [
1693
1830
  ResolveUpdateRequest(target=target, locked_resolve=locked_resolve)
1694
- for target in update_targets
1831
+ for target in update_targets.unique_targets()
1695
1832
  ]
1696
1833
  else:
1697
1834
  # N.B.: With 1 locked resolve in the lock file we're updating, there is no ambiguity
@@ -1709,7 +1846,7 @@ class Lock(OutputMixin, JsonMixin, BuildTimeCommand):
1709
1846
 
1710
1847
  subset_result = try_(
1711
1848
  subset(
1712
- targets=targets,
1849
+ targets=update_targets,
1713
1850
  lock=lock_file,
1714
1851
  network_configuration=pip_configuration.network_configuration,
1715
1852
  build_configuration=lock_file.build_configuration(),
@@ -1723,7 +1860,10 @@ class Lock(OutputMixin, JsonMixin, BuildTimeCommand):
1723
1860
  )
1724
1861
  for resolved_subset in subset_result.subsets
1725
1862
  ]
1726
- if getattr(self.options, "strict", False) and lock_file.style is not LockStyle.UNIVERSAL:
1863
+ if (
1864
+ getattr(self.options, "strict", False)
1865
+ and lock_file.configuration.style is not LockStyle.UNIVERSAL
1866
+ ):
1727
1867
  missing_updates = set(lock_file.locked_resolves) - {
1728
1868
  update_request.locked_resolve for update_request in update_requests
1729
1869
  }
@@ -1737,7 +1877,7 @@ class Lock(OutputMixin, JsonMixin, BuildTimeCommand):
1737
1877
  lock_file=lock_file_path,
1738
1878
  missing_platforms="\n".join(
1739
1879
  sorted(
1740
- "+ {platform}".format(platform=locked_resolve.platform_tag)
1880
+ "+ {platform}".format(platform=locked_resolve.target_platform)
1741
1881
  for locked_resolve in missing_updates
1742
1882
  )
1743
1883
  ),
@@ -2087,10 +2227,7 @@ class Lock(OutputMixin, JsonMixin, BuildTimeCommand):
2087
2227
  original_lock_file = try_(parse_lockfile(self.options, lock_file_path=lock_file_path))
2088
2228
  lock_file = attr.evolve(
2089
2229
  original_lock_file,
2090
- style=lock_configuration.style,
2091
- requires_python=SortedTuple(lock_configuration.requires_python),
2092
- target_systems=SortedTuple(lock_configuration.target_systems),
2093
- elide_unused_requires_dist=lock_configuration.elide_unused_requires_dist,
2230
+ configuration=lock_configuration,
2094
2231
  pip_version=pip_configuration.version,
2095
2232
  resolver_version=pip_configuration.resolver_version,
2096
2233
  allow_prereleases=pip_configuration.allow_prereleases,
@@ -2158,6 +2295,7 @@ class Lock(OutputMixin, JsonMixin, BuildTimeCommand):
2158
2295
  targets=targets,
2159
2296
  pip_configuration=pip_configuration,
2160
2297
  dependency_configuration=dependency_config,
2298
+ avoid_downloads=self.options.avoid_downloads,
2161
2299
  )
2162
2300
  )
2163
2301
  if self.options.dry_run:
@@ -2248,11 +2386,9 @@ class Lock(OutputMixin, JsonMixin, BuildTimeCommand):
2248
2386
  requirements=requirement_configuration.requirements,
2249
2387
  requirement_files=requirement_configuration.requirement_files,
2250
2388
  constraint_files=requirement_configuration.constraint_files,
2251
- indexes=pip_configuration.repos_configuration.indexes,
2252
- find_links=pip_configuration.repos_configuration.find_links,
2389
+ repos_configuration=pip_configuration.repos_configuration,
2253
2390
  resolver_version=pip_configuration.resolver_version,
2254
2391
  network_configuration=pip_configuration.network_configuration,
2255
- password_entries=pip_configuration.repos_configuration.password_entries,
2256
2392
  build_configuration=pip_configuration.build_configuration,
2257
2393
  transitive=pip_configuration.transitive,
2258
2394
  max_parallel_jobs=pip_configuration.max_jobs,
@@ -2261,6 +2397,7 @@ class Lock(OutputMixin, JsonMixin, BuildTimeCommand):
2261
2397
  extra_pip_requirements=pip_configuration.extra_requirements,
2262
2398
  keyring_provider=pip_configuration.keyring_provider,
2263
2399
  result_type=InstallableType.INSTALLED_WHEEL_CHROOT,
2400
+ dependency_configuration=dependency_config,
2264
2401
  )
2265
2402
  )
2266
2403
  return sync_target.sync(