pex 2.58.0__py2.py3-none-any.whl → 2.59.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 (57) hide show
  1. pex/bin/pex.py +1 -0
  2. pex/build_system/pep_517.py +4 -1
  3. pex/cli/commands/venv.py +5 -1
  4. pex/dependency_configuration.py +6 -2
  5. pex/dist_metadata.py +30 -8
  6. pex/docs/html/_pagefind/fragment/en_23dc437.pf_fragment +0 -0
  7. pex/docs/html/_pagefind/fragment/{en_a1e44ad.pf_fragment → en_3b86fa5.pf_fragment} +0 -0
  8. pex/docs/html/_pagefind/fragment/en_65c3a5b.pf_fragment +0 -0
  9. pex/docs/html/_pagefind/fragment/en_6c2e6ff.pf_fragment +0 -0
  10. pex/docs/html/_pagefind/fragment/{en_cb46368.pf_fragment → en_7320871.pf_fragment} +0 -0
  11. pex/docs/html/_pagefind/fragment/en_7cfdecb.pf_fragment +0 -0
  12. pex/docs/html/_pagefind/fragment/en_89e2d7c.pf_fragment +0 -0
  13. pex/docs/html/_pagefind/fragment/{en_1bcc8ee.pf_fragment → en_d4675e3.pf_fragment} +0 -0
  14. pex/docs/html/_pagefind/index/en_34a4497.pf_index +0 -0
  15. pex/docs/html/_pagefind/pagefind-entry.json +1 -1
  16. pex/docs/html/_pagefind/pagefind.en_4ade7b3598.pf_meta +0 -0
  17. pex/docs/html/_static/documentation_options.js +1 -1
  18. pex/docs/html/api/vars.html +5 -5
  19. pex/docs/html/buildingpex.html +5 -5
  20. pex/docs/html/genindex.html +5 -5
  21. pex/docs/html/index.html +5 -5
  22. pex/docs/html/recipes.html +5 -5
  23. pex/docs/html/scie.html +5 -5
  24. pex/docs/html/search.html +5 -5
  25. pex/docs/html/whatispex.html +5 -5
  26. pex/environment.py +17 -3
  27. pex/pep_425.py +11 -2
  28. pex/pep_427.py +218 -73
  29. pex/pip/installation.py +1 -1
  30. pex/pip/tool.py +1 -1
  31. pex/resolve/configured_resolve.py +20 -0
  32. pex/resolve/configured_resolver.py +7 -3
  33. pex/resolve/pex_repository_resolver.py +1 -1
  34. pex/resolve/resolver_configuration.py +17 -0
  35. pex/resolve/resolver_options.py +88 -16
  36. pex/resolve/resolvers.py +3 -0
  37. pex/resolve/target_options.py +18 -2
  38. pex/resolve/venv_resolver.py +446 -0
  39. pex/resolver.py +2 -4
  40. pex/targets.py +9 -4
  41. pex/vendor/__main__.py +1 -1
  42. pex/version.py +1 -1
  43. pex/wheel.py +89 -27
  44. {pex-2.58.0.dist-info → pex-2.59.0.dist-info}/METADATA +4 -4
  45. {pex-2.58.0.dist-info → pex-2.59.0.dist-info}/RECORD +50 -49
  46. pex/docs/html/_pagefind/fragment/en_17c092c.pf_fragment +0 -0
  47. pex/docs/html/_pagefind/fragment/en_4f6b776.pf_fragment +0 -0
  48. pex/docs/html/_pagefind/fragment/en_7be5753.pf_fragment +0 -0
  49. pex/docs/html/_pagefind/fragment/en_dbfc5c3.pf_fragment +0 -0
  50. pex/docs/html/_pagefind/fragment/en_ea2f1cd.pf_fragment +0 -0
  51. pex/docs/html/_pagefind/index/en_a415613.pf_index +0 -0
  52. pex/docs/html/_pagefind/pagefind.en_3d1b9e5425.pf_meta +0 -0
  53. {pex-2.58.0.dist-info → pex-2.59.0.dist-info}/WHEEL +0 -0
  54. {pex-2.58.0.dist-info → pex-2.59.0.dist-info}/entry_points.txt +0 -0
  55. {pex-2.58.0.dist-info → pex-2.59.0.dist-info}/licenses/LICENSE +0 -0
  56. {pex-2.58.0.dist-info → pex-2.59.0.dist-info}/pylock/pylock.toml +0 -0
  57. {pex-2.58.0.dist-info → pex-2.59.0.dist-info}/top_level.txt +0 -0
@@ -5,6 +5,7 @@ from __future__ import absolute_import
5
5
 
6
6
  import glob
7
7
  import os
8
+ import sys
8
9
  import tempfile
9
10
  from argparse import Action, ArgumentError, ArgumentTypeError, Namespace, _ActionsContainer
10
11
  from collections import OrderedDict, defaultdict
@@ -16,6 +17,7 @@ from pex.dist_metadata import Requirement, is_sdist, is_wheel
16
17
  from pex.fetcher import initialize_ssl_context
17
18
  from pex.network_configuration import NetworkConfiguration
18
19
  from pex.orderedset import OrderedSet
20
+ from pex.os import is_exe
19
21
  from pex.pep_503 import ProjectName
20
22
  from pex.pip.version import PipVersion, PipVersionValue
21
23
  from pex.resolve.lockfile import json_codec
@@ -31,39 +33,79 @@ from pex.resolve.resolver_configuration import (
31
33
  PreResolvedConfiguration,
32
34
  PylockRepositoryConfiguration,
33
35
  ResolverVersion,
36
+ VenvRepositoryConfiguration,
34
37
  )
35
38
  from pex.result import Error
36
39
  from pex.tracer import TRACER
37
40
  from pex.typing import TYPE_CHECKING, cast
41
+ from pex.venv.virtualenv import InvalidVirtualenvError, Virtualenv
38
42
 
39
43
  if TYPE_CHECKING:
40
44
  from typing import DefaultDict, Iterable, List, Mapping, Optional, Tuple, Union
41
45
 
42
46
 
43
- class _ManylinuxAction(Action):
47
+ class _HandleTransitiveAction(Action):
44
48
  def __init__(self, *args, **kwargs):
45
- kwargs["nargs"] = "?"
46
- super(_ManylinuxAction, self).__init__(*args, **kwargs)
49
+ kwargs["nargs"] = 0
50
+ super(_HandleTransitiveAction, self).__init__(*args, **kwargs)
47
51
 
48
52
  def __call__(self, parser, namespace, value, option_str=None):
49
- if option_str.startswith("--no"):
50
- setattr(namespace, self.dest, None)
51
- elif value.startswith("manylinux"):
52
- setattr(namespace, self.dest, value)
53
- else:
54
- raise ArgumentTypeError(
55
- "Please specify a manylinux standard; ie: --manylinux=manylinux1. "
56
- "Given {}".format(value)
57
- )
53
+ setattr(namespace, self.dest, option_str == "--transitive")
58
54
 
59
55
 
60
- class _HandleTransitiveAction(Action):
56
+ class _ResolveVenvAction(Action):
61
57
  def __init__(self, *args, **kwargs):
62
- kwargs["nargs"] = 0
63
- super(_HandleTransitiveAction, self).__init__(*args, **kwargs)
58
+ kwargs["nargs"] = "?"
59
+ super(_ResolveVenvAction, self).__init__(*args, **kwargs)
64
60
 
65
61
  def __call__(self, parser, namespace, value, option_str=None):
66
- setattr(namespace, self.dest, option_str == "--transitive")
62
+ if value:
63
+ if not os.path.exists(value):
64
+ raise ArgumentError(
65
+ argument=self,
66
+ message=(
67
+ "The {option} {value} does not point to an existing path.".format(
68
+ option=option_str, value=value
69
+ )
70
+ ),
71
+ )
72
+
73
+ venv = None # type: Optional[Virtualenv]
74
+ if is_exe(value):
75
+ venv = Virtualenv.enclosing(value)
76
+ else:
77
+ try:
78
+ venv = Virtualenv(value)
79
+ except InvalidVirtualenvError:
80
+ pass
81
+ if not venv:
82
+ raise ArgumentError(
83
+ argument=self,
84
+ message=(
85
+ "The {option} {value} is not a valid virtual environment interpreter "
86
+ "path.".format(option=option_str, value=value)
87
+ ),
88
+ )
89
+ setattr(namespace, self.dest, venv)
90
+ else:
91
+ current_venv = Virtualenv.enclosing(python=sys.executable)
92
+ if not current_venv:
93
+ try:
94
+ current_venv = Virtualenv(venv_dir=".")
95
+ except InvalidVirtualenvError:
96
+ raise ArgumentError(
97
+ argument=self,
98
+ message=(
99
+ "The {option} option was requested but Pex is not currently running "
100
+ "from a venv to use as the implicit {option} nor is the current "
101
+ "directory a venv.\n"
102
+ "You'll need to specify the path to a virtual environment directory or "
103
+ "virtual environment interpreter as an argument.".format(
104
+ option=option_str
105
+ )
106
+ ),
107
+ )
108
+ setattr(namespace, self.dest, current_venv)
67
109
 
68
110
 
69
111
  def register(
@@ -72,6 +114,7 @@ def register(
72
114
  include_pex_lock=False, # type: bool
73
115
  include_pylock=False, # type: bool
74
116
  include_pre_resolved=False, # type: bool
117
+ include_venv_repository=False, # type: bool
75
118
  ):
76
119
  # type: (...) -> None
77
120
  """Register resolver configuration options with the given parser.
@@ -82,6 +125,7 @@ def register(
82
125
  :param include_pylock: Whether to include the `--pylock` / `--pep-751-lock` option.
83
126
  :param include_pre_resolved: Whether to include the `--pre-resolved-dist` and
84
127
  `--pre-resolved-dists` options.
128
+ :param include_venv_repository: Whether to include the `--venv-repository` option.
85
129
  """
86
130
 
87
131
  default_resolver_configuration = PipConfiguration()
@@ -192,6 +236,8 @@ def register(
192
236
  repository_types += 1
193
237
  if include_pre_resolved:
194
238
  repository_types += 1
239
+ if include_venv_repository:
240
+ repository_types += 1
195
241
 
196
242
  repository_choice = parser.add_mutually_exclusive_group() if repository_types > 1 else parser
197
243
  if include_pex_repository:
@@ -271,6 +317,19 @@ def register(
271
317
  "default, Pex will ensure the dependencies added form a closure."
272
318
  ),
273
319
  )
320
+ if include_venv_repository:
321
+ repository_choice.add_argument(
322
+ "--venv-repository",
323
+ dest="venv_repository",
324
+ action=_ResolveVenvAction,
325
+ type=str,
326
+ help=(
327
+ "Resolve requirements from the given virtual environment instead of from "
328
+ "--index servers, --find-links repos or a --lock file. The virtual environment to "
329
+ "resolve from can be specified as the path to the venv or the path of its"
330
+ "interpreter. If no value is specified, the current active venv is used."
331
+ ),
332
+ )
274
333
 
275
334
  parser.add_argument(
276
335
  "--pre",
@@ -676,6 +735,7 @@ if TYPE_CHECKING:
676
735
  PipConfiguration,
677
736
  PreResolvedConfiguration,
678
737
  PylockRepositoryConfiguration,
738
+ VenvRepositoryConfiguration,
679
739
  ]
680
740
 
681
741
 
@@ -743,6 +803,18 @@ def configure(
743
803
  sdists=tuple(sdists), wheels=tuple(wheels), pip_configuration=pip_configuration
744
804
  )
745
805
 
806
+ venv = getattr(options, "venv_repository", None)
807
+ if venv:
808
+ return VenvRepositoryConfiguration(venv=venv, pip_configuration=pip_configuration)
809
+
810
+ if pylock:
811
+ return PylockRepositoryConfiguration(
812
+ lock_file_path=pylock,
813
+ extras=frozenset(options.pylock_extras),
814
+ dependency_groups=frozenset(options.pylock_dependency_groups),
815
+ pip_configuration=pip_configuration,
816
+ )
817
+
746
818
  return pip_configuration
747
819
 
748
820
 
pex/resolve/resolvers.py CHANGED
@@ -255,6 +255,9 @@ class Resolver(object):
255
255
  transitive=None, # type: Optional[bool]
256
256
  extra_resolver_requirements=None, # type: Optional[Tuple[Requirement, ...]]
257
257
  result_type=InstallableType.INSTALLED_WHEEL_CHROOT, # type: InstallableType.Value
258
+ constraint_files=None, # type: Optional[Iterable[str]]
259
+ compile=False, # type: bool
260
+ ignore_errors=False, # type: bool
258
261
  ):
259
262
  # type: (...) -> ResolveResult
260
263
  raise NotImplementedError()
@@ -6,7 +6,7 @@ from __future__ import absolute_import
6
6
  import json
7
7
  import os.path
8
8
  import sys
9
- from argparse import ArgumentTypeError, Namespace, _ActionsContainer
9
+ from argparse import Action, ArgumentTypeError, Namespace, _ActionsContainer
10
10
 
11
11
  from pex.argparse import HandleBoolAction
12
12
  from pex.interpreter_constraints import InterpreterConstraints
@@ -16,7 +16,6 @@ from pex.pep_508 import MarkerEnvironment
16
16
  from pex.platforms import Platform, PlatformSpec
17
17
  from pex.resolve import abbreviated_platforms
18
18
  from pex.resolve.resolver_configuration import PipConfiguration
19
- from pex.resolve.resolver_options import _ManylinuxAction
20
19
  from pex.resolve.target_configuration import InterpreterConfiguration, TargetConfiguration
21
20
  from pex.targets import CompletePlatform
22
21
  from pex.typing import TYPE_CHECKING
@@ -25,6 +24,23 @@ if TYPE_CHECKING:
25
24
  from typing import Optional
26
25
 
27
26
 
27
+ class _ManylinuxAction(Action):
28
+ def __init__(self, *args, **kwargs):
29
+ kwargs["nargs"] = "?"
30
+ super(_ManylinuxAction, self).__init__(*args, **kwargs)
31
+
32
+ def __call__(self, parser, namespace, value, option_str=None):
33
+ if option_str.startswith("--no"):
34
+ setattr(namespace, self.dest, None)
35
+ elif value.startswith("manylinux"):
36
+ setattr(namespace, self.dest, value)
37
+ else:
38
+ raise ArgumentTypeError(
39
+ "Please specify a manylinux standard; ie: --manylinux=manylinux1. "
40
+ "Given {}".format(value)
41
+ )
42
+
43
+
28
44
  def register(
29
45
  parser, # type: _ActionsContainer
30
46
  include_platforms=True, # type: bool
@@ -0,0 +1,446 @@
1
+ # Copyright 2025 Pex project contributors.
2
+ # Licensed under the Apache License, Version 2.0 (see LICENSE).
3
+
4
+ from __future__ import absolute_import
5
+
6
+ import functools
7
+ import hashlib
8
+ import itertools
9
+ import os
10
+ from collections import defaultdict, deque
11
+
12
+ from pex import pex_warnings
13
+ from pex.atomic_directory import atomic_directory
14
+ from pex.cache.dirs import CacheDir, InstalledWheelDir
15
+ from pex.common import safe_relative_symlink
16
+ from pex.dependency_configuration import DependencyConfiguration
17
+ from pex.dist_metadata import (
18
+ Constraint,
19
+ Distribution,
20
+ DistributionType,
21
+ MetadataType,
22
+ Requirement,
23
+ find_distribution,
24
+ )
25
+ from pex.exceptions import production_assert, reportable_unexpected_error_msg
26
+ from pex.fingerprinted_distribution import FingerprintedDistribution
27
+ from pex.jobs import DEFAULT_MAX_JOBS, iter_map_parallel
28
+ from pex.orderedset import OrderedSet
29
+ from pex.pep_376 import InstalledWheel
30
+ from pex.pep_427 import InstallableType, InstallableWheel, InstallPaths, install_wheel_chroot
31
+ from pex.pep_503 import ProjectName
32
+ from pex.pip.version import PipVersion
33
+ from pex.requirements import LocalProjectRequirement
34
+ from pex.resolve.configured_resolver import ConfiguredResolver
35
+ from pex.resolve.requirement_configuration import RequirementConfiguration
36
+ from pex.resolve.resolver_configuration import PipConfiguration
37
+ from pex.resolve.resolvers import ResolvedDistribution, Resolver, ResolveResult
38
+ from pex.result import Error
39
+ from pex.targets import LocalInterpreter, Target, Targets
40
+ from pex.typing import TYPE_CHECKING
41
+ from pex.venv.virtualenv import Virtualenv
42
+ from pex.wheel import Wheel
43
+
44
+ if TYPE_CHECKING:
45
+ from typing import DefaultDict, Deque, FrozenSet, Iterable, Iterator, List, Mapping, Set, Union
46
+
47
+ import attr # vendor:skip
48
+ else:
49
+ import pex.third_party.attr as attr
50
+
51
+
52
+ def _install_distribution(
53
+ venv_install_paths, # type: InstallPaths
54
+ distribution, # type: Distribution
55
+ ):
56
+ # type: (...) -> InstalledWheel
57
+
58
+ production_assert(distribution.type is DistributionType.INSTALLED)
59
+ production_assert(distribution.metadata.files.metadata.type is MetadataType.DIST_INFO)
60
+
61
+ wheel = InstallableWheel(
62
+ wheel=Wheel.from_distribution(distribution), install_paths=venv_install_paths
63
+ )
64
+ record_data = wheel.metadata_files.read("RECORD")
65
+ if not record_data:
66
+ raise AssertionError(
67
+ reportable_unexpected_error_msg(
68
+ "Distribution for {project_name} {version} installed at {location} "
69
+ "unexpectedly has no installation RECORD.",
70
+ project_name=distribution.project_name,
71
+ version=distribution.version,
72
+ location=distribution.location,
73
+ )
74
+ )
75
+
76
+ installed_wheel_dir = InstalledWheelDir.create(
77
+ wheel_name=wheel.wheel_file_name, install_hash=hashlib.sha256(record_data).hexdigest()
78
+ )
79
+ with atomic_directory(target_dir=installed_wheel_dir) as atomic_dir:
80
+ if not atomic_dir.is_finalized():
81
+ installed_wheel = install_wheel_chroot(
82
+ wheel,
83
+ atomic_dir.work_dir,
84
+ )
85
+ if not installed_wheel.fingerprint:
86
+ raise AssertionError(reportable_unexpected_error_msg())
87
+ runtime_key_dir = CacheDir.INSTALLED_WHEELS.path(installed_wheel.fingerprint)
88
+ with atomic_directory(runtime_key_dir) as symlink_atomic_dir:
89
+ if not atomic_dir.is_finalized():
90
+ # Note: Create a relative path symlink between the two directories so that the
91
+ # PEX_ROOT can be used within a chroot environment where the prefix of the path
92
+ # may change between programs running inside and outside the chroot.
93
+ safe_relative_symlink(
94
+ installed_wheel_dir,
95
+ os.path.join(symlink_atomic_dir.work_dir, wheel.wheel_file_name),
96
+ )
97
+ return InstalledWheel.load(installed_wheel_dir)
98
+
99
+
100
+ def _install_venv_distributions(
101
+ venv, # type: Virtualenv
102
+ distributions, # type: Iterable[Distribution]
103
+ max_install_jobs=DEFAULT_MAX_JOBS, # type: int
104
+ ):
105
+ # type: (...) -> Iterator[FingerprintedDistribution]
106
+
107
+ venv_install_paths = InstallPaths.interpreter(venv.interpreter)
108
+ for installed_wheel in iter_map_parallel(
109
+ inputs=distributions,
110
+ function=functools.partial(_install_distribution, venv_install_paths),
111
+ max_jobs=max_install_jobs,
112
+ ):
113
+ if not installed_wheel.fingerprint:
114
+ raise AssertionError(reportable_unexpected_error_msg())
115
+ yield FingerprintedDistribution(
116
+ distribution=Distribution.load(installed_wheel.prefix_dir),
117
+ fingerprint=installed_wheel.fingerprint,
118
+ )
119
+
120
+
121
+ @attr.s(frozen=True)
122
+ class ResolveRequirement(object):
123
+ requirement = attr.ib() # type: Requirement
124
+ activated_extras = attr.ib(default=frozenset()) # type: FrozenSet[str]
125
+ required_by = attr.ib(default=None) # type: ResolveRequirement
126
+
127
+ @property
128
+ def project_name(self):
129
+ # type: () -> ProjectName
130
+ return self.requirement.project_name
131
+
132
+ def applies(
133
+ self,
134
+ target, # type: Target
135
+ dependency_configuration=DependencyConfiguration(), # type: DependencyConfiguration
136
+ ):
137
+ # type: (...) -> bool
138
+
139
+ if dependency_configuration.excluded_by(self.requirement):
140
+ return False
141
+
142
+ return target.requirement_applies(
143
+ requirement=self.requirement, extras=self.activated_extras
144
+ )
145
+
146
+ def contains(
147
+ self,
148
+ distribution, # type: Distribution
149
+ prereleases=False, # type: bool
150
+ ):
151
+ # type: (...) -> bool
152
+ return self.requirement.contains(distribution, prereleases=prereleases)
153
+
154
+ def dependency(
155
+ self,
156
+ requirement, # type: Requirement
157
+ target, # type: Target
158
+ dependency_configuration=DependencyConfiguration(), # type: DependencyConfiguration
159
+ ):
160
+ # type: (...) -> ResolveRequirement
161
+ return ResolveRequirement(
162
+ requirement=dependency_configuration.overridden_by(requirement, target) or requirement,
163
+ activated_extras=self.requirement.extras,
164
+ required_by=self,
165
+ )
166
+
167
+ def __str__(self):
168
+ # type: () -> str
169
+
170
+ if not self.required_by:
171
+ return "top level requirement {requirement}".format(requirement=self.requirement)
172
+
173
+ return "{required_by} -> {requirement}".format(
174
+ required_by=self.required_by, requirement=self.requirement
175
+ )
176
+
177
+
178
+ def _resolve_distributions(
179
+ venv, # type: Virtualenv
180
+ resolver, # type: Resolver
181
+ target, # type: Target
182
+ search_path, # type: Iterable[str]
183
+ requirements, # type: Iterable[Requirement]
184
+ constraints_by_project_name, # type: Mapping[ProjectName, Iterable[Constraint]]
185
+ dependency_configuration=DependencyConfiguration(), # type: DependencyConfiguration
186
+ allow_prereleases=False, # type: bool
187
+ compile=False, # type: bool
188
+ ignore_errors=False, # type: bool
189
+ ):
190
+ # type: (...) -> Iterator[Union[Distribution, FingerprintedDistribution, Error]]
191
+
192
+ def meets_requirement(
193
+ selected_distribution, # type: Distribution
194
+ requirement, # type: ResolveRequirement
195
+ ):
196
+ # type: (...) -> bool
197
+
198
+ if not requirement.contains(selected_distribution, prereleases=allow_prereleases):
199
+ return False
200
+
201
+ constraints = [
202
+ constraint
203
+ for constraint in constraints_by_project_name[
204
+ selected_distribution.metadata.project_name
205
+ ]
206
+ if target.requirement_applies(constraint, extras=requirement.activated_extras)
207
+ ]
208
+ if not constraints:
209
+ return True
210
+
211
+ return all(
212
+ constraint.contains(selected_distribution, prereleases=allow_prereleases)
213
+ for constraint in constraints
214
+ )
215
+
216
+ def error(message):
217
+ # type: (str) -> Error
218
+ return Error(
219
+ "Resolve from venv at {venv} failed: {message}".format(
220
+ venv=venv.venv_dir, message=message
221
+ )
222
+ )
223
+
224
+ to_resolve = deque(
225
+ OrderedSet(ResolveRequirement(requirement) for requirement in requirements)
226
+ ) # type: Deque[ResolveRequirement]
227
+ resolved = set() # type: Set[ProjectName]
228
+ while to_resolve:
229
+ requirement = to_resolve.popleft()
230
+ if requirement.project_name in resolved:
231
+ continue
232
+
233
+ if not requirement.applies(target, dependency_configuration):
234
+ continue
235
+
236
+ distribution = find_distribution(requirement.project_name, search_path)
237
+ if not distribution:
238
+ yield error(
239
+ "The virtual environment does not have {project_name} installed but it is required "
240
+ "by {requirement}".format(
241
+ project_name=requirement.project_name, requirement=requirement
242
+ )
243
+ )
244
+ elif meets_requirement(distribution, requirement):
245
+ production_assert(distribution.type is DistributionType.INSTALLED)
246
+ resolved.add(requirement.project_name)
247
+ if distribution.metadata.files.metadata.type is MetadataType.DIST_INFO:
248
+ to_resolve.extend(
249
+ requirement.dependency(
250
+ requirement=dependency,
251
+ target=target,
252
+ dependency_configuration=dependency_configuration,
253
+ )
254
+ for dependency in distribution.metadata.requires_dists
255
+ )
256
+ yield distribution
257
+ else:
258
+ result = resolver.resolve_requirements(
259
+ requirements=[str(distribution.as_requirement())],
260
+ targets=Targets.from_target(target),
261
+ transitive=False,
262
+ compile=compile,
263
+ ignore_errors=ignore_errors,
264
+ )
265
+ for dist in result.distributions:
266
+ to_resolve.extend(
267
+ requirement.dependency(
268
+ requirement=dependency,
269
+ target=target,
270
+ dependency_configuration=dependency_configuration,
271
+ )
272
+ for dependency in dist.distribution.metadata.requires_dists
273
+ )
274
+ yield dist.fingerprinted_distribution
275
+ else:
276
+ yield error(
277
+ "The virtual environment has {project_name} {version} installed but it does not "
278
+ "meet {requirement}{suffix}.".format(
279
+ project_name=distribution.project_name,
280
+ version=distribution.version,
281
+ requirement=requirement,
282
+ suffix=(
283
+ " due to supplied constraints"
284
+ if requirement.contains(distribution, prereleases=allow_prereleases)
285
+ else ""
286
+ ),
287
+ )
288
+ )
289
+
290
+
291
+ def resolve_from_venv(
292
+ targets, # type: Targets
293
+ venv, # type: Virtualenv
294
+ requirement_configuration=RequirementConfiguration(), # type: RequirementConfiguration
295
+ pip_configuration=PipConfiguration(), # type: PipConfiguration
296
+ compile=False, # type: bool
297
+ ignore_errors=False, # type: bool
298
+ result_type=InstallableType.INSTALLED_WHEEL_CHROOT, # type: InstallableType.Value
299
+ dependency_configuration=DependencyConfiguration(), # type: DependencyConfiguration
300
+ ):
301
+ # type: (...) -> Union[ResolveResult, Error]
302
+
303
+ if result_type is InstallableType.WHEEL_FILE:
304
+ return Error(
305
+ "Cannot resolve .whl files from virtual environment at {venv_dir}; its distributions "
306
+ "are all installed.".format(venv_dir=venv.venv_dir)
307
+ )
308
+
309
+ target = LocalInterpreter.create(venv.interpreter)
310
+ if not targets.is_empty:
311
+ return Error(
312
+ "You configured custom targets via --python, --interpreter-constraint, --platform or "
313
+ "--complete-platform but custom targets are not allowed when resolving from a virtual "
314
+ "environment.\n"
315
+ "For such resolves, the supported target is implicitly the one matching the venv "
316
+ "interpreter; in this case: {target}.".format(target=target.render_description())
317
+ )
318
+
319
+ if pip_configuration.version:
320
+ compatible_pip_version = (
321
+ pip_configuration.version
322
+ if pip_configuration.version.requires_python_applies(target)
323
+ else PipVersion.latest_compatible(target)
324
+ )
325
+ if pip_configuration.version != compatible_pip_version:
326
+ if pip_configuration.allow_version_fallback:
327
+ pex_warnings.warn(
328
+ "Adjusted Pip version from {version} to {compatible_version} to work with the "
329
+ "venv interpreter.".format(
330
+ version=pip_configuration.version, compatible_version=compatible_pip_version
331
+ )
332
+ )
333
+ else:
334
+ return Error(
335
+ "Pip version {version} is not compatible with the Python {python_version} "
336
+ "venv interpreter.".format(
337
+ version=pip_configuration.version, python_version=venv.interpreter.python
338
+ )
339
+ )
340
+ elif PipVersion.DEFAULT.requires_python_applies(target):
341
+ compatible_pip_version = PipVersion.DEFAULT
342
+ else:
343
+ compatible_pip_version = PipVersion.latest_compatible(target)
344
+
345
+ resolver = ConfiguredResolver(
346
+ pip_configuration=attr.evolve(pip_configuration, version=compatible_pip_version)
347
+ )
348
+ fingerprinted_distributions = [] # type: List[FingerprintedDistribution]
349
+ venv_distributions = [] # type: List[Distribution]
350
+ direct_requirements_by_project_name = defaultdict(
351
+ OrderedSet
352
+ ) # type: DefaultDict[ProjectName, OrderedSet[Requirement]]
353
+ if requirement_configuration.has_requirements:
354
+ parsed_requirements = requirement_configuration.parse_requirements(
355
+ network_configuration=pip_configuration.network_configuration
356
+ )
357
+ local_project_requirements = OrderedSet() # type: OrderedSet[LocalProjectRequirement]
358
+ root_requirements = OrderedSet() # type: OrderedSet[Requirement]
359
+ for parsed_requirement in parsed_requirements:
360
+ if isinstance(parsed_requirement, LocalProjectRequirement):
361
+ local_project_requirements.add(parsed_requirement)
362
+ else:
363
+ root_requirements.add(parsed_requirement.requirement)
364
+ direct_requirements_by_project_name[
365
+ parsed_requirement.requirement.project_name
366
+ ].add(parsed_requirement.requirement)
367
+
368
+ if local_project_requirements:
369
+ return Error(
370
+ "Local project directory requirements cannot be resolved from venvs.\n"
371
+ "Use the project name instead if it is installed in the venv."
372
+ )
373
+
374
+ constraints_by_project_name = defaultdict(
375
+ OrderedSet
376
+ ) # type: DefaultDict[ProjectName, OrderedSet[Constraint]]
377
+ for constraint in requirement_configuration.parse_constraints(
378
+ network_configuration=pip_configuration.network_configuration
379
+ ):
380
+ constraints_by_project_name[constraint.project_name].add(constraint.requirement)
381
+
382
+ for distribution_or_error in _resolve_distributions(
383
+ venv=venv,
384
+ resolver=resolver,
385
+ target=target,
386
+ search_path=tuple(
387
+ site_packages_dir.path for site_packages_dir in venv.interpreter.site_packages
388
+ ),
389
+ requirements=root_requirements,
390
+ constraints_by_project_name=constraints_by_project_name,
391
+ dependency_configuration=dependency_configuration,
392
+ allow_prereleases=pip_configuration.allow_prereleases,
393
+ compile=compile,
394
+ ignore_errors=ignore_errors,
395
+ ):
396
+ if isinstance(distribution_or_error, Error):
397
+ return distribution_or_error
398
+ elif isinstance(distribution_or_error, FingerprintedDistribution):
399
+ fingerprinted_distributions.append(distribution_or_error)
400
+ else:
401
+ venv_distributions.append(distribution_or_error)
402
+ else:
403
+ sdists_to_resolve = []
404
+ for venv_distribution in venv.iter_distributions():
405
+ if venv_distribution.metadata.files.metadata.type is not MetadataType.DIST_INFO:
406
+ sdists_to_resolve.append(str(venv_distribution.as_requirement()))
407
+ else:
408
+ venv_distributions.append(venv_distribution)
409
+ direct_requirements_by_project_name[venv_distribution.metadata.project_name].add(
410
+ venv_distribution.as_requirement()
411
+ )
412
+ result = resolver.resolve_requirements(
413
+ sdists_to_resolve,
414
+ constraint_files=requirement_configuration.constraint_files,
415
+ targets=Targets.from_target(target),
416
+ transitive=False,
417
+ compile=compile,
418
+ ignore_errors=ignore_errors,
419
+ )
420
+ fingerprinted_distributions.extend(
421
+ dist.fingerprinted_distribution for dist in result.distributions
422
+ )
423
+
424
+ return ResolveResult(
425
+ dependency_configuration=dependency_configuration,
426
+ distributions=tuple(
427
+ ResolvedDistribution(
428
+ target=target,
429
+ fingerprinted_distribution=fingerprinted_distribution,
430
+ direct_requirements=direct_requirements_by_project_name[
431
+ fingerprinted_distribution.project_name
432
+ ],
433
+ )
434
+ for fingerprinted_distribution in itertools.chain(
435
+ list(
436
+ _install_venv_distributions(
437
+ venv=venv,
438
+ distributions=venv_distributions,
439
+ max_install_jobs=pip_configuration.max_jobs,
440
+ )
441
+ ),
442
+ fingerprinted_distributions,
443
+ )
444
+ ),
445
+ type=result_type,
446
+ )