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.
- pex/bin/pex.py +1 -0
- pex/build_system/pep_517.py +4 -1
- pex/cli/commands/venv.py +5 -1
- pex/dependency_configuration.py +6 -2
- pex/dist_metadata.py +30 -8
- pex/docs/html/_pagefind/fragment/en_23dc437.pf_fragment +0 -0
- pex/docs/html/_pagefind/fragment/{en_a1e44ad.pf_fragment → en_3b86fa5.pf_fragment} +0 -0
- pex/docs/html/_pagefind/fragment/en_65c3a5b.pf_fragment +0 -0
- pex/docs/html/_pagefind/fragment/en_6c2e6ff.pf_fragment +0 -0
- pex/docs/html/_pagefind/fragment/{en_cb46368.pf_fragment → en_7320871.pf_fragment} +0 -0
- pex/docs/html/_pagefind/fragment/en_7cfdecb.pf_fragment +0 -0
- pex/docs/html/_pagefind/fragment/en_89e2d7c.pf_fragment +0 -0
- pex/docs/html/_pagefind/fragment/{en_1bcc8ee.pf_fragment → en_d4675e3.pf_fragment} +0 -0
- pex/docs/html/_pagefind/index/en_34a4497.pf_index +0 -0
- pex/docs/html/_pagefind/pagefind-entry.json +1 -1
- pex/docs/html/_pagefind/pagefind.en_4ade7b3598.pf_meta +0 -0
- pex/docs/html/_static/documentation_options.js +1 -1
- pex/docs/html/api/vars.html +5 -5
- pex/docs/html/buildingpex.html +5 -5
- pex/docs/html/genindex.html +5 -5
- pex/docs/html/index.html +5 -5
- pex/docs/html/recipes.html +5 -5
- pex/docs/html/scie.html +5 -5
- pex/docs/html/search.html +5 -5
- pex/docs/html/whatispex.html +5 -5
- pex/environment.py +17 -3
- pex/pep_425.py +11 -2
- pex/pep_427.py +218 -73
- pex/pip/installation.py +1 -1
- pex/pip/tool.py +1 -1
- pex/resolve/configured_resolve.py +20 -0
- pex/resolve/configured_resolver.py +7 -3
- pex/resolve/pex_repository_resolver.py +1 -1
- pex/resolve/resolver_configuration.py +17 -0
- pex/resolve/resolver_options.py +88 -16
- pex/resolve/resolvers.py +3 -0
- pex/resolve/target_options.py +18 -2
- pex/resolve/venv_resolver.py +446 -0
- pex/resolver.py +2 -4
- pex/targets.py +9 -4
- pex/vendor/__main__.py +1 -1
- pex/version.py +1 -1
- pex/wheel.py +89 -27
- {pex-2.58.0.dist-info → pex-2.59.0.dist-info}/METADATA +4 -4
- {pex-2.58.0.dist-info → pex-2.59.0.dist-info}/RECORD +50 -49
- pex/docs/html/_pagefind/fragment/en_17c092c.pf_fragment +0 -0
- pex/docs/html/_pagefind/fragment/en_4f6b776.pf_fragment +0 -0
- pex/docs/html/_pagefind/fragment/en_7be5753.pf_fragment +0 -0
- pex/docs/html/_pagefind/fragment/en_dbfc5c3.pf_fragment +0 -0
- pex/docs/html/_pagefind/fragment/en_ea2f1cd.pf_fragment +0 -0
- pex/docs/html/_pagefind/index/en_a415613.pf_index +0 -0
- pex/docs/html/_pagefind/pagefind.en_3d1b9e5425.pf_meta +0 -0
- {pex-2.58.0.dist-info → pex-2.59.0.dist-info}/WHEEL +0 -0
- {pex-2.58.0.dist-info → pex-2.59.0.dist-info}/entry_points.txt +0 -0
- {pex-2.58.0.dist-info → pex-2.59.0.dist-info}/licenses/LICENSE +0 -0
- {pex-2.58.0.dist-info → pex-2.59.0.dist-info}/pylock/pylock.toml +0 -0
- {pex-2.58.0.dist-info → pex-2.59.0.dist-info}/top_level.txt +0 -0
pex/resolve/resolver_options.py
CHANGED
|
@@ -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
|
|
47
|
+
class _HandleTransitiveAction(Action):
|
|
44
48
|
def __init__(self, *args, **kwargs):
|
|
45
|
-
kwargs["nargs"] =
|
|
46
|
-
super(
|
|
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
|
-
|
|
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
|
|
56
|
+
class _ResolveVenvAction(Action):
|
|
61
57
|
def __init__(self, *args, **kwargs):
|
|
62
|
-
kwargs["nargs"] =
|
|
63
|
-
super(
|
|
58
|
+
kwargs["nargs"] = "?"
|
|
59
|
+
super(_ResolveVenvAction, self).__init__(*args, **kwargs)
|
|
64
60
|
|
|
65
61
|
def __call__(self, parser, namespace, value, option_str=None):
|
|
66
|
-
|
|
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()
|
pex/resolve/target_options.py
CHANGED
|
@@ -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
|
+
)
|