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.
- pex/auth.py +1 -1
- pex/bin/pex.py +15 -2
- pex/build_backend/configuration.py +5 -5
- pex/build_backend/wrap.py +27 -23
- pex/build_system/pep_517.py +4 -1
- pex/cache/dirs.py +17 -12
- pex/cli/commands/lock.py +302 -165
- pex/cli/commands/pip/core.py +4 -12
- pex/cli/commands/pip/wheel.py +1 -1
- pex/cli/commands/run.py +13 -20
- pex/cli/commands/venv.py +85 -16
- pex/cli/pex.py +11 -4
- pex/common.py +57 -7
- pex/compatibility.py +1 -1
- pex/dependency_configuration.py +87 -15
- pex/dist_metadata.py +143 -25
- pex/docs/html/_pagefind/fragment/en_4250138.pf_fragment +0 -0
- pex/docs/html/_pagefind/fragment/en_7125dad.pf_fragment +0 -0
- pex/docs/html/_pagefind/fragment/en_785d562.pf_fragment +0 -0
- pex/docs/html/_pagefind/fragment/en_8e94bb8.pf_fragment +0 -0
- pex/docs/html/_pagefind/fragment/en_a0396bb.pf_fragment +0 -0
- pex/docs/html/_pagefind/fragment/en_a8a3588.pf_fragment +0 -0
- pex/docs/html/_pagefind/fragment/en_c07d988.pf_fragment +0 -0
- pex/docs/html/_pagefind/fragment/en_d718411.pf_fragment +0 -0
- pex/docs/html/_pagefind/index/en_a2e3c5e.pf_index +0 -0
- pex/docs/html/_pagefind/pagefind-entry.json +1 -1
- pex/docs/html/_pagefind/pagefind.en_4ce1afa9e3.pf_meta +0 -0
- pex/docs/html/_static/documentation_options.js +1 -1
- pex/docs/html/_static/pygments.css +164 -146
- pex/docs/html/_static/styles/furo.css +1 -1
- pex/docs/html/_static/styles/furo.css.map +1 -1
- pex/docs/html/api/vars.html +25 -34
- pex/docs/html/buildingpex.html +25 -34
- pex/docs/html/genindex.html +24 -33
- pex/docs/html/index.html +25 -34
- pex/docs/html/recipes.html +25 -34
- pex/docs/html/scie.html +25 -34
- pex/docs/html/search.html +24 -33
- pex/docs/html/whatispex.html +25 -34
- pex/entry_points_txt.py +98 -0
- pex/environment.py +54 -33
- pex/finders.py +1 -1
- pex/hashing.py +71 -9
- pex/installed_wheel.py +141 -0
- pex/interpreter.py +41 -38
- pex/interpreter_constraints.py +25 -25
- pex/interpreter_implementation.py +40 -0
- pex/jobs.py +13 -6
- pex/pep_376.py +68 -384
- pex/pep_425.py +11 -2
- pex/pep_427.py +937 -205
- pex/pep_508.py +4 -5
- pex/pex_builder.py +5 -8
- pex/pex_info.py +14 -9
- pex/pip/dependencies/__init__.py +85 -13
- pex/pip/dependencies/requires.py +38 -3
- pex/pip/foreign_platform/__init__.py +4 -3
- pex/pip/installation.py +2 -2
- pex/pip/local_project.py +6 -14
- pex/pip/package_repositories/__init__.py +78 -0
- pex/pip/package_repositories/link_collector.py +96 -0
- pex/pip/tool.py +139 -33
- pex/pip/vcs.py +109 -43
- pex/pip/version.py +8 -1
- pex/requirements.py +121 -16
- pex/resolve/config.py +5 -1
- pex/resolve/configured_resolve.py +32 -10
- pex/resolve/configured_resolver.py +10 -39
- pex/resolve/downloads.py +4 -3
- pex/resolve/lock_downloader.py +16 -23
- pex/resolve/lock_resolver.py +41 -51
- pex/resolve/locked_resolve.py +89 -32
- pex/resolve/locker.py +145 -101
- pex/resolve/locker_patches.py +123 -197
- pex/resolve/lockfile/create.py +232 -87
- pex/resolve/lockfile/download_manager.py +5 -1
- pex/resolve/lockfile/json_codec.py +103 -28
- pex/resolve/lockfile/model.py +13 -35
- pex/resolve/lockfile/pep_751.py +117 -98
- pex/resolve/lockfile/requires_dist.py +17 -262
- pex/resolve/lockfile/subset.py +11 -0
- pex/resolve/lockfile/targets.py +445 -0
- pex/resolve/lockfile/updater.py +22 -10
- pex/resolve/package_repository.py +406 -0
- pex/resolve/pex_repository_resolver.py +1 -1
- pex/resolve/pre_resolved_resolver.py +19 -16
- pex/resolve/project.py +233 -47
- pex/resolve/requirement_configuration.py +28 -10
- pex/resolve/resolver_configuration.py +18 -32
- pex/resolve/resolver_options.py +234 -28
- pex/resolve/resolvers.py +3 -12
- pex/resolve/target_options.py +18 -2
- pex/resolve/target_system.py +908 -0
- pex/resolve/venv_resolver.py +670 -0
- pex/resolver.py +673 -209
- pex/scie/__init__.py +40 -1
- pex/scie/model.py +2 -0
- pex/scie/science.py +25 -3
- pex/sdist.py +219 -0
- pex/sh_boot.py +24 -21
- pex/sysconfig.py +5 -3
- pex/targets.py +31 -10
- pex/third_party/__init__.py +1 -1
- pex/tools/commands/repository.py +48 -25
- pex/vendor/__init__.py +4 -9
- pex/vendor/__main__.py +65 -41
- pex/vendor/_vendored/ansicolors/.layout.json +1 -1
- pex/vendor/_vendored/ansicolors/ansicolors-1.1.8.dist-info/RECORD +11 -0
- pex/vendor/_vendored/ansicolors/ansicolors-1.1.8.pex-info/original-whl-info.json +1 -0
- pex/vendor/_vendored/appdirs/.layout.json +1 -1
- pex/vendor/_vendored/appdirs/appdirs-1.4.4.dist-info/RECORD +7 -0
- pex/vendor/_vendored/appdirs/appdirs-1.4.4.pex-info/original-whl-info.json +1 -0
- pex/vendor/_vendored/attrs/.layout.json +1 -1
- pex/vendor/_vendored/attrs/attrs-21.5.0.dev0.dist-info/RECORD +37 -0
- pex/vendor/_vendored/attrs/attrs-21.5.0.dev0.pex-info/original-whl-info.json +1 -0
- pex/vendor/_vendored/packaging_20_9/.layout.json +1 -1
- pex/vendor/_vendored/packaging_20_9/packaging-20.9.dist-info/RECORD +20 -0
- pex/vendor/_vendored/packaging_20_9/packaging-20.9.pex-info/original-whl-info.json +1 -0
- pex/vendor/_vendored/packaging_20_9/pyparsing-2.4.7.dist-info/RECORD +7 -0
- pex/vendor/_vendored/packaging_20_9/pyparsing-2.4.7.pex-info/original-whl-info.json +1 -0
- pex/vendor/_vendored/packaging_21_3/.layout.json +1 -1
- pex/vendor/_vendored/packaging_21_3/packaging-21.3.dist-info/RECORD +20 -0
- pex/vendor/_vendored/packaging_21_3/packaging-21.3.pex-info/original-whl-info.json +1 -0
- pex/vendor/_vendored/packaging_21_3/pyparsing-3.0.7.dist-info/RECORD +18 -0
- pex/vendor/_vendored/packaging_21_3/pyparsing-3.0.7.pex-info/original-whl-info.json +1 -0
- pex/vendor/_vendored/packaging_24_0/.layout.json +1 -1
- pex/vendor/_vendored/packaging_24_0/packaging-24.0.dist-info/RECORD +22 -0
- pex/vendor/_vendored/packaging_24_0/packaging-24.0.pex-info/original-whl-info.json +1 -0
- pex/vendor/_vendored/packaging_25_0/.layout.json +1 -1
- pex/vendor/_vendored/packaging_25_0/packaging-25.0.dist-info/RECORD +24 -0
- pex/vendor/_vendored/packaging_25_0/packaging-25.0.pex-info/original-whl-info.json +1 -0
- pex/vendor/_vendored/pip/.layout.json +1 -1
- pex/vendor/_vendored/pip/pip/_vendor/certifi/cacert.pem +63 -1
- pex/vendor/_vendored/pip/pip-20.3.4.dist-info/RECORD +388 -0
- pex/vendor/_vendored/pip/pip-20.3.4.pex-info/original-whl-info.json +1 -0
- pex/vendor/_vendored/setuptools/.layout.json +1 -1
- pex/vendor/_vendored/setuptools/setuptools-44.0.0+3acb925dd708430aeaf197ea53ac8a752f7c1863.dist-info/RECORD +107 -0
- pex/vendor/_vendored/setuptools/setuptools-44.0.0+3acb925dd708430aeaf197ea53ac8a752f7c1863.pex-info/original-whl-info.json +1 -0
- pex/vendor/_vendored/toml/.layout.json +1 -1
- pex/vendor/_vendored/toml/toml-0.10.2.dist-info/RECORD +11 -0
- pex/vendor/_vendored/toml/toml-0.10.2.pex-info/original-whl-info.json +1 -0
- pex/vendor/_vendored/tomli/.layout.json +1 -1
- pex/vendor/_vendored/tomli/tomli-2.0.1.dist-info/RECORD +10 -0
- pex/vendor/_vendored/tomli/tomli-2.0.1.pex-info/original-whl-info.json +1 -0
- pex/venv/installer.py +46 -19
- pex/venv/venv_pex.py +6 -3
- pex/version.py +1 -1
- pex/wheel.py +188 -40
- pex/whl.py +67 -0
- pex/windows/__init__.py +14 -11
- {pex-2.54.2.dist-info → pex-2.69.0.dist-info}/METADATA +6 -5
- {pex-2.54.2.dist-info → pex-2.69.0.dist-info}/RECORD +157 -133
- {pex-2.54.2.dist-info → pex-2.69.0.dist-info}/entry_points.txt +1 -0
- {pex-2.54.2.dist-info → pex-2.69.0.dist-info}/pylock/pylock.toml +1 -1
- pex/docs/html/_pagefind/fragment/en_42c9d8c.pf_fragment +0 -0
- pex/docs/html/_pagefind/fragment/en_45dd5a2.pf_fragment +0 -0
- pex/docs/html/_pagefind/fragment/en_4ca74d2.pf_fragment +0 -0
- pex/docs/html/_pagefind/fragment/en_77273d5.pf_fragment +0 -0
- pex/docs/html/_pagefind/fragment/en_87a59c5.pf_fragment +0 -0
- pex/docs/html/_pagefind/fragment/en_8dc89b5.pf_fragment +0 -0
- pex/docs/html/_pagefind/fragment/en_9d1319b.pf_fragment +0 -0
- pex/docs/html/_pagefind/fragment/en_e55df9d.pf_fragment +0 -0
- pex/docs/html/_pagefind/index/en_1e98c6f.pf_index +0 -0
- pex/docs/html/_pagefind/pagefind.en_d1c488ecae.pf_meta +0 -0
- pex/vendor/_vendored/ansicolors/ansicolors-1.1.8.dist-info/INSTALLER +0 -1
- pex/vendor/_vendored/appdirs/appdirs-1.4.4.dist-info/INSTALLER +0 -1
- pex/vendor/_vendored/attrs/attrs-21.5.0.dev0.dist-info/INSTALLER +0 -1
- pex/vendor/_vendored/packaging_20_9/packaging-20.9.dist-info/INSTALLER +0 -1
- pex/vendor/_vendored/packaging_20_9/pyparsing-2.4.7.dist-info/INSTALLER +0 -1
- pex/vendor/_vendored/packaging_21_3/packaging-21.3.dist-info/INSTALLER +0 -1
- pex/vendor/_vendored/packaging_21_3/pyparsing-3.0.7.dist-info/INSTALLER +0 -1
- pex/vendor/_vendored/packaging_24_0/packaging-24.0.dist-info/INSTALLER +0 -1
- pex/vendor/_vendored/packaging_25_0/packaging-25.0.dist-info/INSTALLER +0 -1
- pex/vendor/_vendored/pip/pip-20.3.4.dist-info/INSTALLER +0 -1
- pex/vendor/_vendored/setuptools/setuptools-44.0.0+3acb925dd708430aeaf197ea53ac8a752f7c1863.dist-info/INSTALLER +0 -1
- pex/vendor/_vendored/toml/toml-0.10.2.dist-info/INSTALLER +0 -1
- pex/vendor/_vendored/tomli/tomli-2.0.1.dist-info/INSTALLER +0 -1
- {pex-2.54.2.dist-info → pex-2.69.0.dist-info}/WHEEL +0 -0
- {pex-2.54.2.dist-info → pex-2.69.0.dist-info}/licenses/LICENSE +0 -0
- {pex-2.54.2.dist-info → pex-2.69.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,670 @@
|
|
|
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 os
|
|
9
|
+
from collections import defaultdict, deque
|
|
10
|
+
|
|
11
|
+
from pex import pex_warnings
|
|
12
|
+
from pex.atomic_directory import atomic_directory
|
|
13
|
+
from pex.cache.dirs import CacheDir, InstalledWheelDir
|
|
14
|
+
from pex.common import pluralize, safe_relative_symlink
|
|
15
|
+
from pex.compatibility import commonpath
|
|
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.installed_wheel import InstalledWheel
|
|
28
|
+
from pex.jobs import DEFAULT_MAX_JOBS, iter_map_parallel
|
|
29
|
+
from pex.orderedset import OrderedSet
|
|
30
|
+
from pex.pep_376 import InstalledDirectory, InstalledFile, Record
|
|
31
|
+
from pex.pep_427 import InstallableType, InstallableWheel, InstallPaths, install_wheel_chroot
|
|
32
|
+
from pex.pep_503 import ProjectName
|
|
33
|
+
from pex.pip.version import PipVersion
|
|
34
|
+
from pex.requirements import LocalProjectRequirement
|
|
35
|
+
from pex.resolve.configured_resolver import ConfiguredResolver
|
|
36
|
+
from pex.resolve.requirement_configuration import RequirementConfiguration
|
|
37
|
+
from pex.resolve.resolver_configuration import PipConfiguration
|
|
38
|
+
from pex.resolve.resolvers import ResolvedDistribution, Resolver, ResolveResult
|
|
39
|
+
from pex.result import Error
|
|
40
|
+
from pex.sysconfig import script_name
|
|
41
|
+
from pex.targets import LocalInterpreter, Target, Targets
|
|
42
|
+
from pex.typing import TYPE_CHECKING
|
|
43
|
+
from pex.venv.virtualenv import Virtualenv
|
|
44
|
+
from pex.wheel import WHEEL, Wheel
|
|
45
|
+
from pex.whl import repacked_whl
|
|
46
|
+
|
|
47
|
+
if TYPE_CHECKING:
|
|
48
|
+
from typing import (
|
|
49
|
+
DefaultDict,
|
|
50
|
+
Deque,
|
|
51
|
+
FrozenSet,
|
|
52
|
+
Iterable,
|
|
53
|
+
Iterator,
|
|
54
|
+
List,
|
|
55
|
+
Mapping,
|
|
56
|
+
Set,
|
|
57
|
+
Tuple,
|
|
58
|
+
Union,
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
import attr # vendor:skip
|
|
62
|
+
else:
|
|
63
|
+
import pex.third_party.attr as attr
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _normalize_record(
|
|
67
|
+
distribution, # type: Distribution
|
|
68
|
+
install_paths, # type: InstallPaths
|
|
69
|
+
record_data, # type: bytes
|
|
70
|
+
):
|
|
71
|
+
# type: (...) -> bytes
|
|
72
|
+
|
|
73
|
+
entry_map = distribution.get_entry_map()
|
|
74
|
+
entry_point_scripts = {
|
|
75
|
+
script_name(entry_point)
|
|
76
|
+
for key in ("console_scripts", "gui_scripts")
|
|
77
|
+
for entry_point in entry_map.get(key, {})
|
|
78
|
+
}
|
|
79
|
+
if not entry_point_scripts:
|
|
80
|
+
return record_data
|
|
81
|
+
|
|
82
|
+
scripts_dir = os.path.realpath(install_paths.scripts)
|
|
83
|
+
record_lines = record_data.decode("utf-8").splitlines(True) # N.B. no kw in 2.7: keepends=True
|
|
84
|
+
eol = os.sep
|
|
85
|
+
if record_lines:
|
|
86
|
+
eol = "\r\n" if record_lines[0].endswith("\r\n") else "\n"
|
|
87
|
+
|
|
88
|
+
installed_files = [] # type: List[Union[InstalledFile, InstalledDirectory]]
|
|
89
|
+
for installed_file in Record.read(lines=iter(record_lines)):
|
|
90
|
+
if isinstance(installed_file, InstalledDirectory):
|
|
91
|
+
installed_files.append(installed_file)
|
|
92
|
+
elif isinstance(installed_file, InstalledFile) and (
|
|
93
|
+
(os.path.basename(installed_file.path) not in entry_point_scripts)
|
|
94
|
+
or (
|
|
95
|
+
scripts_dir
|
|
96
|
+
!= commonpath(
|
|
97
|
+
(
|
|
98
|
+
scripts_dir,
|
|
99
|
+
os.path.realpath(os.path.join(distribution.location, installed_file.path)),
|
|
100
|
+
)
|
|
101
|
+
)
|
|
102
|
+
)
|
|
103
|
+
):
|
|
104
|
+
installed_files.append(installed_file)
|
|
105
|
+
return Record.write_bytes(installed_files=installed_files, eol=eol)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _install_distribution(
|
|
109
|
+
venv_distribution, # type: VenvDistribution
|
|
110
|
+
result_type, # type: InstallableType.Value
|
|
111
|
+
use_system_time, # type: bool
|
|
112
|
+
):
|
|
113
|
+
# type: (...) -> ResolvedDistribution
|
|
114
|
+
|
|
115
|
+
interpreter = venv_distribution.target.interpreter
|
|
116
|
+
distribution = venv_distribution.distribution
|
|
117
|
+
|
|
118
|
+
production_assert(distribution.type is DistributionType.INSTALLED)
|
|
119
|
+
production_assert(distribution.metadata.files.metadata.type is MetadataType.DIST_INFO)
|
|
120
|
+
|
|
121
|
+
venv_install_paths = InstallPaths.interpreter(
|
|
122
|
+
interpreter,
|
|
123
|
+
project_name=distribution.metadata.project_name,
|
|
124
|
+
root_is_purelib=WHEEL.from_distribution(distribution).root_is_purelib,
|
|
125
|
+
)
|
|
126
|
+
wheel = InstallableWheel.from_whl(
|
|
127
|
+
whl=Wheel.from_distribution(distribution), install_paths=venv_install_paths
|
|
128
|
+
)
|
|
129
|
+
record_data = wheel.metadata_files.read("RECORD")
|
|
130
|
+
if not record_data:
|
|
131
|
+
raise AssertionError(
|
|
132
|
+
reportable_unexpected_error_msg(
|
|
133
|
+
"Distribution for {project_name} {version} installed at {location} "
|
|
134
|
+
"unexpectedly has no installation RECORD.",
|
|
135
|
+
project_name=distribution.project_name,
|
|
136
|
+
version=distribution.version,
|
|
137
|
+
location=distribution.location,
|
|
138
|
+
)
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
installed_wheel_dir = InstalledWheelDir.create(
|
|
142
|
+
wheel_name=wheel.wheel_file_name,
|
|
143
|
+
install_hash=hashlib.sha256(
|
|
144
|
+
_normalize_record(
|
|
145
|
+
distribution=Distribution(location=wheel.location, metadata=wheel.dist_metadata()),
|
|
146
|
+
install_paths=venv_install_paths,
|
|
147
|
+
record_data=record_data,
|
|
148
|
+
)
|
|
149
|
+
).hexdigest(),
|
|
150
|
+
)
|
|
151
|
+
with atomic_directory(target_dir=installed_wheel_dir) as atomic_dir:
|
|
152
|
+
if not atomic_dir.is_finalized():
|
|
153
|
+
installed_wheel = install_wheel_chroot(
|
|
154
|
+
wheel,
|
|
155
|
+
atomic_dir.work_dir,
|
|
156
|
+
)
|
|
157
|
+
if not installed_wheel.fingerprint:
|
|
158
|
+
raise AssertionError(reportable_unexpected_error_msg())
|
|
159
|
+
runtime_key_dir = CacheDir.INSTALLED_WHEELS.path(installed_wheel.fingerprint)
|
|
160
|
+
with atomic_directory(runtime_key_dir) as symlink_atomic_dir:
|
|
161
|
+
if not symlink_atomic_dir.is_finalized():
|
|
162
|
+
# Note: Create a relative path symlink between the two directories so that the
|
|
163
|
+
# PEX_ROOT can be used within a chroot environment where the prefix of the path
|
|
164
|
+
# may change between programs running inside and outside the chroot.
|
|
165
|
+
safe_relative_symlink(
|
|
166
|
+
installed_wheel_dir,
|
|
167
|
+
os.path.join(symlink_atomic_dir.work_dir, wheel.wheel_file_name),
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
installed_wheel = InstalledWheel.load(installed_wheel_dir)
|
|
171
|
+
if not installed_wheel.fingerprint:
|
|
172
|
+
raise AssertionError(reportable_unexpected_error_msg())
|
|
173
|
+
|
|
174
|
+
if result_type is InstallableType.INSTALLED_WHEEL_CHROOT:
|
|
175
|
+
return ResolvedDistribution(
|
|
176
|
+
target=venv_distribution.target,
|
|
177
|
+
fingerprinted_distribution=FingerprintedDistribution(
|
|
178
|
+
distribution=Distribution.load(installed_wheel.prefix_dir),
|
|
179
|
+
fingerprint=installed_wheel.fingerprint,
|
|
180
|
+
),
|
|
181
|
+
direct_requirements=venv_distribution.direct_requirements,
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
return ResolvedDistribution(
|
|
185
|
+
target=venv_distribution.target,
|
|
186
|
+
fingerprinted_distribution=repacked_whl(
|
|
187
|
+
installed_wheel,
|
|
188
|
+
fingerprint=installed_wheel.fingerprint,
|
|
189
|
+
use_system_time=use_system_time,
|
|
190
|
+
),
|
|
191
|
+
direct_requirements=venv_distribution.direct_requirements,
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
@attr.s(frozen=True)
|
|
196
|
+
class VenvDistribution(object):
|
|
197
|
+
target = attr.ib() # type: LocalInterpreter
|
|
198
|
+
distribution = attr.ib() # type: Distribution
|
|
199
|
+
direct_requirements = attr.ib() # type: Iterable[Requirement]
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def _install_venv_distributions(
|
|
203
|
+
venv_resolve_results, # type: Iterable[VenvResolveResult]
|
|
204
|
+
max_install_jobs=DEFAULT_MAX_JOBS, # type: int
|
|
205
|
+
result_type=InstallableType.INSTALLED_WHEEL_CHROOT, # type: InstallableType.Value
|
|
206
|
+
use_system_time=False, # type: bool
|
|
207
|
+
):
|
|
208
|
+
# type: (...) -> Iterator[ResolvedDistribution]
|
|
209
|
+
|
|
210
|
+
seen = set() # type: Set[str]
|
|
211
|
+
|
|
212
|
+
venv_distributions = [] # type: List[VenvDistribution]
|
|
213
|
+
for venv_resolve_result in venv_resolve_results:
|
|
214
|
+
target = venv_resolve_result.target
|
|
215
|
+
direct_requirements = venv_resolve_result.direct_requirements_by_project_name
|
|
216
|
+
for re_resolved_distribution in venv_resolve_result.re_resolved_distributions:
|
|
217
|
+
wheel_file_name = Wheel.from_distribution(
|
|
218
|
+
re_resolved_distribution.distribution
|
|
219
|
+
).wheel_file_name
|
|
220
|
+
if wheel_file_name in seen:
|
|
221
|
+
continue
|
|
222
|
+
|
|
223
|
+
seen.add(wheel_file_name)
|
|
224
|
+
yield ResolvedDistribution(
|
|
225
|
+
target=target,
|
|
226
|
+
fingerprinted_distribution=re_resolved_distribution,
|
|
227
|
+
direct_requirements=direct_requirements.get(
|
|
228
|
+
re_resolved_distribution.project_name, ()
|
|
229
|
+
),
|
|
230
|
+
)
|
|
231
|
+
for venv_distribution in venv_resolve_result.venv_distributions:
|
|
232
|
+
wheel_file_name = Wheel.from_distribution(venv_distribution).wheel_file_name
|
|
233
|
+
if wheel_file_name in seen:
|
|
234
|
+
continue
|
|
235
|
+
|
|
236
|
+
seen.add(wheel_file_name)
|
|
237
|
+
venv_distributions.append(
|
|
238
|
+
VenvDistribution(
|
|
239
|
+
target=target,
|
|
240
|
+
distribution=venv_distribution,
|
|
241
|
+
direct_requirements=direct_requirements.get(
|
|
242
|
+
venv_distribution.metadata.project_name, ()
|
|
243
|
+
),
|
|
244
|
+
)
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
for resolved_distribution in iter_map_parallel(
|
|
248
|
+
inputs=venv_distributions,
|
|
249
|
+
function=functools.partial(
|
|
250
|
+
_install_distribution, result_type=result_type, use_system_time=use_system_time
|
|
251
|
+
),
|
|
252
|
+
max_jobs=max_install_jobs,
|
|
253
|
+
):
|
|
254
|
+
yield resolved_distribution
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
@attr.s(frozen=True)
|
|
258
|
+
class ResolveRequirement(object):
|
|
259
|
+
requirement = attr.ib() # type: Requirement
|
|
260
|
+
activated_extras = attr.ib(default=frozenset()) # type: FrozenSet[str]
|
|
261
|
+
required_by = attr.ib(default=None) # type: ResolveRequirement
|
|
262
|
+
|
|
263
|
+
@property
|
|
264
|
+
def project_name(self):
|
|
265
|
+
# type: () -> ProjectName
|
|
266
|
+
return self.requirement.project_name
|
|
267
|
+
|
|
268
|
+
def applies(
|
|
269
|
+
self,
|
|
270
|
+
target, # type: Target
|
|
271
|
+
dependency_configuration=DependencyConfiguration(), # type: DependencyConfiguration
|
|
272
|
+
):
|
|
273
|
+
# type: (...) -> bool
|
|
274
|
+
|
|
275
|
+
if dependency_configuration.excluded_by(self.requirement):
|
|
276
|
+
return False
|
|
277
|
+
|
|
278
|
+
return target.requirement_applies(
|
|
279
|
+
requirement=self.requirement, extras=self.activated_extras
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
def contains(
|
|
283
|
+
self,
|
|
284
|
+
distribution, # type: Distribution
|
|
285
|
+
prereleases=False, # type: bool
|
|
286
|
+
):
|
|
287
|
+
# type: (...) -> bool
|
|
288
|
+
return self.requirement.contains(distribution, prereleases=prereleases)
|
|
289
|
+
|
|
290
|
+
def dependency(
|
|
291
|
+
self,
|
|
292
|
+
requirement, # type: Requirement
|
|
293
|
+
target, # type: Target
|
|
294
|
+
dependency_configuration=DependencyConfiguration(), # type: DependencyConfiguration
|
|
295
|
+
):
|
|
296
|
+
# type: (...) -> ResolveRequirement
|
|
297
|
+
return ResolveRequirement(
|
|
298
|
+
requirement=dependency_configuration.overridden_by(requirement, target) or requirement,
|
|
299
|
+
activated_extras=self.requirement.extras,
|
|
300
|
+
required_by=self,
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
def __str__(self):
|
|
304
|
+
# type: () -> str
|
|
305
|
+
|
|
306
|
+
if not self.required_by:
|
|
307
|
+
return "top level requirement {requirement}".format(requirement=self.requirement)
|
|
308
|
+
|
|
309
|
+
return "{required_by} -> {requirement}".format(
|
|
310
|
+
required_by=self.required_by, requirement=self.requirement
|
|
311
|
+
)
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
def _resolve_distributions(
|
|
315
|
+
venv, # type: Virtualenv
|
|
316
|
+
resolver, # type: Resolver
|
|
317
|
+
target, # type: Target
|
|
318
|
+
search_path, # type: Iterable[str]
|
|
319
|
+
requirements, # type: Iterable[Requirement]
|
|
320
|
+
constraints_by_project_name, # type: Mapping[ProjectName, Iterable[Constraint]]
|
|
321
|
+
dependency_configuration=DependencyConfiguration(), # type: DependencyConfiguration
|
|
322
|
+
allow_prereleases=False, # type: bool
|
|
323
|
+
compile=False, # type: bool
|
|
324
|
+
ignore_errors=False, # type: bool
|
|
325
|
+
result_type=InstallableType.INSTALLED_WHEEL_CHROOT, # type: InstallableType.Value
|
|
326
|
+
):
|
|
327
|
+
# type: (...) -> Iterator[Union[Distribution, FingerprintedDistribution, Error]]
|
|
328
|
+
|
|
329
|
+
def meets_requirement(
|
|
330
|
+
selected_distribution, # type: Distribution
|
|
331
|
+
requirement, # type: ResolveRequirement
|
|
332
|
+
):
|
|
333
|
+
# type: (...) -> bool
|
|
334
|
+
|
|
335
|
+
if not requirement.contains(selected_distribution, prereleases=allow_prereleases):
|
|
336
|
+
return False
|
|
337
|
+
|
|
338
|
+
constraints = [
|
|
339
|
+
constraint
|
|
340
|
+
for constraint in constraints_by_project_name[
|
|
341
|
+
selected_distribution.metadata.project_name
|
|
342
|
+
]
|
|
343
|
+
if target.requirement_applies(constraint, extras=requirement.activated_extras)
|
|
344
|
+
]
|
|
345
|
+
if not constraints:
|
|
346
|
+
return True
|
|
347
|
+
|
|
348
|
+
return all(
|
|
349
|
+
constraint.contains(selected_distribution, prereleases=allow_prereleases)
|
|
350
|
+
for constraint in constraints
|
|
351
|
+
)
|
|
352
|
+
|
|
353
|
+
def error(message):
|
|
354
|
+
# type: (str) -> Error
|
|
355
|
+
return Error(
|
|
356
|
+
"Resolve from venv at {venv} failed: {message}".format(
|
|
357
|
+
venv=venv.venv_dir, message=message
|
|
358
|
+
)
|
|
359
|
+
)
|
|
360
|
+
|
|
361
|
+
to_resolve = deque(
|
|
362
|
+
OrderedSet(ResolveRequirement(requirement) for requirement in requirements)
|
|
363
|
+
) # type: Deque[ResolveRequirement]
|
|
364
|
+
resolved = set() # type: Set[ProjectName]
|
|
365
|
+
while to_resolve:
|
|
366
|
+
requirement = to_resolve.popleft()
|
|
367
|
+
if requirement.project_name in resolved:
|
|
368
|
+
continue
|
|
369
|
+
|
|
370
|
+
if not requirement.applies(target, dependency_configuration):
|
|
371
|
+
continue
|
|
372
|
+
|
|
373
|
+
distribution = find_distribution(requirement.project_name, search_path)
|
|
374
|
+
if not distribution:
|
|
375
|
+
yield error(
|
|
376
|
+
"The virtual environment does not have {project_name} installed but it is required "
|
|
377
|
+
"by {requirement}".format(
|
|
378
|
+
project_name=requirement.project_name, requirement=requirement
|
|
379
|
+
)
|
|
380
|
+
)
|
|
381
|
+
elif meets_requirement(distribution, requirement):
|
|
382
|
+
production_assert(distribution.type is DistributionType.INSTALLED)
|
|
383
|
+
resolved.add(requirement.project_name)
|
|
384
|
+
editable_project_url = distribution.editable_install_url()
|
|
385
|
+
if (
|
|
386
|
+
not editable_project_url
|
|
387
|
+
and distribution.metadata.files.metadata.type is MetadataType.DIST_INFO
|
|
388
|
+
):
|
|
389
|
+
to_resolve.extend(
|
|
390
|
+
requirement.dependency(
|
|
391
|
+
requirement=dependency,
|
|
392
|
+
target=target,
|
|
393
|
+
dependency_configuration=dependency_configuration,
|
|
394
|
+
)
|
|
395
|
+
for dependency in distribution.metadata.requires_dists
|
|
396
|
+
)
|
|
397
|
+
yield distribution
|
|
398
|
+
else:
|
|
399
|
+
source_requirement = (
|
|
400
|
+
"{project} @ {url}".format(
|
|
401
|
+
project=distribution.metadata.project_name, url=editable_project_url
|
|
402
|
+
)
|
|
403
|
+
if editable_project_url
|
|
404
|
+
else str(distribution.as_requirement())
|
|
405
|
+
)
|
|
406
|
+
result = resolver.resolve_requirements(
|
|
407
|
+
requirements=[source_requirement],
|
|
408
|
+
targets=Targets.from_target(target),
|
|
409
|
+
transitive=False,
|
|
410
|
+
compile=compile,
|
|
411
|
+
ignore_errors=ignore_errors,
|
|
412
|
+
result_type=result_type,
|
|
413
|
+
)
|
|
414
|
+
for dist in result.distributions:
|
|
415
|
+
to_resolve.extend(
|
|
416
|
+
requirement.dependency(
|
|
417
|
+
requirement=dependency,
|
|
418
|
+
target=target,
|
|
419
|
+
dependency_configuration=dependency_configuration,
|
|
420
|
+
)
|
|
421
|
+
for dependency in dist.distribution.metadata.requires_dists
|
|
422
|
+
)
|
|
423
|
+
yield dist.fingerprinted_distribution
|
|
424
|
+
else:
|
|
425
|
+
yield error(
|
|
426
|
+
"The virtual environment has {project_name} {version} installed but it does not "
|
|
427
|
+
"meet {requirement}{suffix}.".format(
|
|
428
|
+
project_name=distribution.project_name,
|
|
429
|
+
version=distribution.version,
|
|
430
|
+
requirement=requirement,
|
|
431
|
+
suffix=(
|
|
432
|
+
" due to supplied constraints"
|
|
433
|
+
if requirement.contains(distribution, prereleases=allow_prereleases)
|
|
434
|
+
else ""
|
|
435
|
+
),
|
|
436
|
+
)
|
|
437
|
+
)
|
|
438
|
+
|
|
439
|
+
|
|
440
|
+
@attr.s(frozen=True)
|
|
441
|
+
class VenvResolveResult(object):
|
|
442
|
+
venv = attr.ib() # type: Virtualenv
|
|
443
|
+
venv_distributions = attr.ib() # type: Tuple[Distribution, ...]
|
|
444
|
+
re_resolved_distributions = attr.ib() # type: Tuple[FingerprintedDistribution, ...]
|
|
445
|
+
direct_requirements_by_project_name = attr.ib(
|
|
446
|
+
eq=False
|
|
447
|
+
) # type: Mapping[ProjectName, Iterable[Requirement]]
|
|
448
|
+
|
|
449
|
+
@property
|
|
450
|
+
def target(self):
|
|
451
|
+
# type: () -> LocalInterpreter
|
|
452
|
+
return LocalInterpreter.create(self.venv.interpreter)
|
|
453
|
+
|
|
454
|
+
|
|
455
|
+
def _resolve_from_venv(
|
|
456
|
+
venv, # type: Virtualenv
|
|
457
|
+
requirement_configuration, # type: RequirementConfiguration
|
|
458
|
+
pip_configuration, # type: PipConfiguration
|
|
459
|
+
compile, # type: bool
|
|
460
|
+
ignore_errors, # type: bool
|
|
461
|
+
result_type, # type: InstallableType.Value
|
|
462
|
+
dependency_configuration, # type: DependencyConfiguration
|
|
463
|
+
):
|
|
464
|
+
# type: (...) -> Union[VenvResolveResult, Error]
|
|
465
|
+
target = LocalInterpreter.create(venv.interpreter)
|
|
466
|
+
|
|
467
|
+
if pip_configuration.version:
|
|
468
|
+
compatible_pip_version = (
|
|
469
|
+
pip_configuration.version
|
|
470
|
+
if pip_configuration.version.requires_python_applies(target)
|
|
471
|
+
else PipVersion.latest_compatible(target)
|
|
472
|
+
)
|
|
473
|
+
if pip_configuration.version != compatible_pip_version:
|
|
474
|
+
if pip_configuration.allow_version_fallback:
|
|
475
|
+
pex_warnings.warn(
|
|
476
|
+
"Adjusted Pip version from {version} to {compatible_version} to work with the "
|
|
477
|
+
"venv interpreter.".format(
|
|
478
|
+
version=pip_configuration.version, compatible_version=compatible_pip_version
|
|
479
|
+
)
|
|
480
|
+
)
|
|
481
|
+
else:
|
|
482
|
+
return Error(
|
|
483
|
+
"Pip version {version} is not compatible with the Python {python_version} "
|
|
484
|
+
"venv interpreter.".format(
|
|
485
|
+
version=pip_configuration.version, python_version=venv.interpreter.python
|
|
486
|
+
)
|
|
487
|
+
)
|
|
488
|
+
elif PipVersion.DEFAULT.requires_python_applies(target):
|
|
489
|
+
compatible_pip_version = PipVersion.DEFAULT
|
|
490
|
+
else:
|
|
491
|
+
compatible_pip_version = PipVersion.latest_compatible(target)
|
|
492
|
+
|
|
493
|
+
resolver = ConfiguredResolver(
|
|
494
|
+
pip_configuration=attr.evolve(pip_configuration, version=compatible_pip_version)
|
|
495
|
+
)
|
|
496
|
+
fingerprinted_distributions = [] # type: List[FingerprintedDistribution]
|
|
497
|
+
venv_distributions = [] # type: List[Distribution]
|
|
498
|
+
direct_requirements_by_project_name = defaultdict(
|
|
499
|
+
OrderedSet
|
|
500
|
+
) # type: DefaultDict[ProjectName, OrderedSet[Requirement]]
|
|
501
|
+
if requirement_configuration.has_requirements:
|
|
502
|
+
parsed_requirements = requirement_configuration.parse_requirements(
|
|
503
|
+
network_configuration=pip_configuration.network_configuration
|
|
504
|
+
)
|
|
505
|
+
local_project_requirements = OrderedSet() # type: OrderedSet[LocalProjectRequirement]
|
|
506
|
+
root_requirements = OrderedSet() # type: OrderedSet[Requirement]
|
|
507
|
+
for parsed_requirement in parsed_requirements:
|
|
508
|
+
if isinstance(parsed_requirement, LocalProjectRequirement):
|
|
509
|
+
local_project_requirements.add(parsed_requirement)
|
|
510
|
+
else:
|
|
511
|
+
root_requirements.add(parsed_requirement.requirement)
|
|
512
|
+
direct_requirements_by_project_name[
|
|
513
|
+
parsed_requirement.requirement.project_name
|
|
514
|
+
].add(parsed_requirement.requirement)
|
|
515
|
+
|
|
516
|
+
if local_project_requirements:
|
|
517
|
+
return Error(
|
|
518
|
+
"Local project directory requirements cannot be resolved from venvs.\n"
|
|
519
|
+
"Use the project name instead if it is installed in the venv."
|
|
520
|
+
)
|
|
521
|
+
|
|
522
|
+
constraints_by_project_name = defaultdict(
|
|
523
|
+
OrderedSet
|
|
524
|
+
) # type: DefaultDict[ProjectName, OrderedSet[Constraint]]
|
|
525
|
+
for constraint in requirement_configuration.parse_constraints(
|
|
526
|
+
network_configuration=pip_configuration.network_configuration
|
|
527
|
+
):
|
|
528
|
+
constraints_by_project_name[constraint.project_name].add(constraint.requirement)
|
|
529
|
+
|
|
530
|
+
for distribution_or_error in _resolve_distributions(
|
|
531
|
+
venv=venv,
|
|
532
|
+
resolver=resolver,
|
|
533
|
+
target=target,
|
|
534
|
+
search_path=tuple(
|
|
535
|
+
site_packages_dir.path for site_packages_dir in venv.interpreter.site_packages
|
|
536
|
+
),
|
|
537
|
+
requirements=root_requirements,
|
|
538
|
+
constraints_by_project_name=constraints_by_project_name,
|
|
539
|
+
dependency_configuration=dependency_configuration,
|
|
540
|
+
allow_prereleases=pip_configuration.allow_prereleases,
|
|
541
|
+
compile=compile,
|
|
542
|
+
ignore_errors=ignore_errors,
|
|
543
|
+
result_type=result_type,
|
|
544
|
+
):
|
|
545
|
+
if isinstance(distribution_or_error, Error):
|
|
546
|
+
return distribution_or_error
|
|
547
|
+
elif isinstance(distribution_or_error, FingerprintedDistribution):
|
|
548
|
+
fingerprinted_distributions.append(distribution_or_error)
|
|
549
|
+
else:
|
|
550
|
+
venv_distributions.append(distribution_or_error)
|
|
551
|
+
else:
|
|
552
|
+
sdists_to_resolve = []
|
|
553
|
+
for venv_distribution in venv.iter_distributions():
|
|
554
|
+
if venv_distribution.metadata.files.metadata.type is not MetadataType.DIST_INFO:
|
|
555
|
+
sdists_to_resolve.append(str(venv_distribution.as_requirement()))
|
|
556
|
+
else:
|
|
557
|
+
editable_project_url = venv_distribution.editable_install_url()
|
|
558
|
+
if editable_project_url:
|
|
559
|
+
sdists_to_resolve.append(
|
|
560
|
+
"{project_name} @ {url}".format(
|
|
561
|
+
project_name=venv_distribution.metadata.project_name,
|
|
562
|
+
url=editable_project_url,
|
|
563
|
+
)
|
|
564
|
+
)
|
|
565
|
+
else:
|
|
566
|
+
venv_distributions.append(venv_distribution)
|
|
567
|
+
|
|
568
|
+
direct_requirements_by_project_name[venv_distribution.metadata.project_name].add(
|
|
569
|
+
venv_distribution.as_requirement()
|
|
570
|
+
)
|
|
571
|
+
result = resolver.resolve_requirements(
|
|
572
|
+
sdists_to_resolve,
|
|
573
|
+
constraint_files=requirement_configuration.constraint_files,
|
|
574
|
+
targets=Targets.from_target(target),
|
|
575
|
+
transitive=False,
|
|
576
|
+
compile=compile,
|
|
577
|
+
ignore_errors=ignore_errors,
|
|
578
|
+
)
|
|
579
|
+
fingerprinted_distributions.extend(
|
|
580
|
+
dist.fingerprinted_distribution for dist in result.distributions
|
|
581
|
+
)
|
|
582
|
+
|
|
583
|
+
return VenvResolveResult(
|
|
584
|
+
venv=venv,
|
|
585
|
+
venv_distributions=tuple(venv_distributions),
|
|
586
|
+
re_resolved_distributions=tuple(fingerprinted_distributions),
|
|
587
|
+
direct_requirements_by_project_name=direct_requirements_by_project_name,
|
|
588
|
+
)
|
|
589
|
+
|
|
590
|
+
|
|
591
|
+
def resolve_from_venvs(
|
|
592
|
+
targets, # type: Targets
|
|
593
|
+
venvs, # type: Tuple[Virtualenv, ...]
|
|
594
|
+
requirement_configuration=RequirementConfiguration(), # type: RequirementConfiguration
|
|
595
|
+
pip_configuration=PipConfiguration(), # type: PipConfiguration
|
|
596
|
+
compile=False, # type: bool
|
|
597
|
+
ignore_errors=False, # type: bool
|
|
598
|
+
result_type=InstallableType.INSTALLED_WHEEL_CHROOT, # type: InstallableType.Value
|
|
599
|
+
dependency_configuration=DependencyConfiguration(), # type: DependencyConfiguration
|
|
600
|
+
):
|
|
601
|
+
# type: (...) -> Union[ResolveResult, Error]
|
|
602
|
+
|
|
603
|
+
if not targets.is_empty:
|
|
604
|
+
return Error(
|
|
605
|
+
"You configured custom targets via --python, --interpreter-constraint, --platform or "
|
|
606
|
+
"--complete-platform but custom targets are not allowed when resolving from {venvs}.\n"
|
|
607
|
+
"For such resolves, the supported target is implicitly the one matching the venv "
|
|
608
|
+
"{interpreters}; in this case:{targets}.".format(
|
|
609
|
+
venvs="a virtual environment" if len(venvs) == 1 else "virtual environments",
|
|
610
|
+
interpreters=pluralize(venvs, "interpreter"),
|
|
611
|
+
targets=(
|
|
612
|
+
" {target}".format(
|
|
613
|
+
target=LocalInterpreter.create(venvs[0].interpreter).render_description()
|
|
614
|
+
)
|
|
615
|
+
if len(venvs) == 1
|
|
616
|
+
else "\n {targets}".format(
|
|
617
|
+
targets="\n ".join(
|
|
618
|
+
LocalInterpreter.create(venv.interpreter).render_description()
|
|
619
|
+
for venv in venvs
|
|
620
|
+
)
|
|
621
|
+
)
|
|
622
|
+
),
|
|
623
|
+
)
|
|
624
|
+
)
|
|
625
|
+
|
|
626
|
+
errors = [] # type: List[Error]
|
|
627
|
+
venv_resolve_results = [] # type: List[VenvResolveResult]
|
|
628
|
+
for result in iter_map_parallel(
|
|
629
|
+
venvs,
|
|
630
|
+
functools.partial(
|
|
631
|
+
_resolve_from_venv,
|
|
632
|
+
requirement_configuration=requirement_configuration,
|
|
633
|
+
pip_configuration=pip_configuration,
|
|
634
|
+
compile=compile,
|
|
635
|
+
ignore_errors=ignore_errors,
|
|
636
|
+
result_type=result_type,
|
|
637
|
+
dependency_configuration=dependency_configuration,
|
|
638
|
+
),
|
|
639
|
+
):
|
|
640
|
+
if isinstance(result, Error):
|
|
641
|
+
errors.append(result)
|
|
642
|
+
else:
|
|
643
|
+
venv_resolve_results.append(result)
|
|
644
|
+
|
|
645
|
+
if len(errors) == 1:
|
|
646
|
+
return errors[0]
|
|
647
|
+
elif errors:
|
|
648
|
+
return Error(
|
|
649
|
+
"Failed to resolve from {count} of {total} virtual environments:\n{failures}".format(
|
|
650
|
+
count=len(errors),
|
|
651
|
+
total=len(venvs),
|
|
652
|
+
failures="\n".join(
|
|
653
|
+
"{index}. {error}".format(index=index, error=error)
|
|
654
|
+
for index, error in enumerate(errors, start=1)
|
|
655
|
+
),
|
|
656
|
+
)
|
|
657
|
+
)
|
|
658
|
+
|
|
659
|
+
return ResolveResult(
|
|
660
|
+
dependency_configuration=dependency_configuration,
|
|
661
|
+
distributions=tuple(
|
|
662
|
+
_install_venv_distributions(
|
|
663
|
+
venv_resolve_results,
|
|
664
|
+
max_install_jobs=pip_configuration.max_jobs,
|
|
665
|
+
result_type=result_type,
|
|
666
|
+
use_system_time=True,
|
|
667
|
+
)
|
|
668
|
+
),
|
|
669
|
+
type=result_type,
|
|
670
|
+
)
|