nab-python 0.0.1__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.
- nab_python/__init__.py +1 -0
- nab_python/_build/__init__.py +1 -0
- nab_python/_build/env.py +364 -0
- nab_python/_build/errors.py +17 -0
- nab_python/_build/runner.py +254 -0
- nab_python/_lockfile/__init__.py +1 -0
- nab_python/_lockfile/builder.py +339 -0
- nab_python/_lockfile/disjointness.py +207 -0
- nab_python/_lockfile/pylock.py +323 -0
- nab_python/_lockfile/requirements.py +121 -0
- nab_python/_packaging_provider.py +98 -0
- nab_python/_provider/__init__.py +1 -0
- nab_python/_provider/build_remote.py +95 -0
- nab_python/_provider/extras.py +231 -0
- nab_python/_provider/listing.py +442 -0
- nab_python/_provider/lookahead.py +156 -0
- nab_python/_provider/metadata_resolver.py +450 -0
- nab_python/_provider/priority.py +174 -0
- nab_python/_provider/sources.py +215 -0
- nab_python/_testing/__init__.py +1 -0
- nab_python/_testing/coordinator_fake.py +240 -0
- nab_python/_vcs_admission.py +209 -0
- nab_python/_vendor/__init__.py +6 -0
- nab_python/_vendor/packaging/LICENSE +3 -0
- nab_python/_vendor/packaging/LICENSE.APACHE +177 -0
- nab_python/_vendor/packaging/LICENSE.BSD +23 -0
- nab_python/_vendor/packaging/PROVENANCE.md +73 -0
- nab_python/_vendor/packaging/__init__.py +15 -0
- nab_python/_vendor/packaging/_elffile.py +108 -0
- nab_python/_vendor/packaging/_manylinux.py +265 -0
- nab_python/_vendor/packaging/_musllinux.py +88 -0
- nab_python/_vendor/packaging/_parser.py +394 -0
- nab_python/_vendor/packaging/_structures.py +33 -0
- nab_python/_vendor/packaging/_tokenizer.py +196 -0
- nab_python/_vendor/packaging/dependency_groups.py +302 -0
- nab_python/_vendor/packaging/direct_url.py +325 -0
- nab_python/_vendor/packaging/errors.py +94 -0
- nab_python/_vendor/packaging/licenses/__init__.py +186 -0
- nab_python/_vendor/packaging/licenses/_spdx.py +799 -0
- nab_python/_vendor/packaging/markers.py +506 -0
- nab_python/_vendor/packaging/metadata.py +964 -0
- nab_python/_vendor/packaging/py.typed +0 -0
- nab_python/_vendor/packaging/pylock.py +910 -0
- nab_python/_vendor/packaging/ranges.py +1803 -0
- nab_python/_vendor/packaging/requirements.py +132 -0
- nab_python/_vendor/packaging/specifiers.py +1141 -0
- nab_python/_vendor/packaging/tags.py +929 -0
- nab_python/_vendor/packaging/utils.py +296 -0
- nab_python/_vendor/packaging/version.py +1230 -0
- nab_python/build_backend.py +184 -0
- nab_python/config.py +805 -0
- nab_python/download.py +170 -0
- nab_python/fetch.py +827 -0
- nab_python/lockfile.py +238 -0
- nab_python/metadata.py +145 -0
- nab_python/provider.py +1235 -0
- nab_python/py.typed +0 -0
- nab_python/requirements_file.py +180 -0
- nab_python/resolve.py +497 -0
- nab_python/universal/__init__.py +1 -0
- nab_python/universal/matrix.py +235 -0
- nab_python/universal/provider.py +214 -0
- nab_python/universal/reresolve.py +310 -0
- nab_python/universal/resolve.py +508 -0
- nab_python/universal/validate.py +439 -0
- nab_python/universal/wheel_selection.py +327 -0
- nab_python/workspace.py +214 -0
- nab_python-0.0.1.dist-info/METADATA +49 -0
- nab_python-0.0.1.dist-info/RECORD +71 -0
- nab_python-0.0.1.dist-info/WHEEL +4 -0
- nab_python-0.0.1.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,508 @@
|
|
|
1
|
+
"""Universal resolution loop.
|
|
2
|
+
|
|
3
|
+
Runs one specific resolve per matrix tuple, sharing a single
|
|
4
|
+
FetchCoordinator so metadata is fetched once across tuples. Returns a
|
|
5
|
+
merged result keyed by package name with the markers under which each
|
|
6
|
+
pin applies.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import logging
|
|
12
|
+
import time
|
|
13
|
+
from collections import defaultdict
|
|
14
|
+
from dataclasses import dataclass, field
|
|
15
|
+
from typing import TYPE_CHECKING
|
|
16
|
+
|
|
17
|
+
from nab_index.multi_index import IndexConfig
|
|
18
|
+
from nab_index.urllib3_async_transport import Urllib3AsyncTransport
|
|
19
|
+
from nab_resolver.errors import ResolutionError
|
|
20
|
+
from nab_resolver.resolver import Resolver
|
|
21
|
+
|
|
22
|
+
from .._vendor.packaging.markers import Marker
|
|
23
|
+
from .._vendor.packaging.ranges import VersionRange
|
|
24
|
+
from .._vendor.packaging.requirements import InvalidRequirement, Requirement
|
|
25
|
+
from .._vendor.packaging.utils import canonicalize_name
|
|
26
|
+
from ..fetch import (
|
|
27
|
+
DEFAULT_INDEX_NAME,
|
|
28
|
+
DEFAULT_INDEX_URL,
|
|
29
|
+
FetchCoordinator,
|
|
30
|
+
IndexOverride,
|
|
31
|
+
)
|
|
32
|
+
from ..lockfile import (
|
|
33
|
+
LockInput,
|
|
34
|
+
MissingHashError,
|
|
35
|
+
PinShape,
|
|
36
|
+
build_lock_input_from_provider,
|
|
37
|
+
)
|
|
38
|
+
from ..provider import (
|
|
39
|
+
BuildPolicy,
|
|
40
|
+
DistPolicy,
|
|
41
|
+
LocalSource,
|
|
42
|
+
VcsConfig,
|
|
43
|
+
VcsSource,
|
|
44
|
+
split_extra,
|
|
45
|
+
)
|
|
46
|
+
from .provider import UniversalProvider
|
|
47
|
+
|
|
48
|
+
__all__ = [
|
|
49
|
+
"TupleResult",
|
|
50
|
+
"UniversalResult",
|
|
51
|
+
"merge_universal_lock_inputs",
|
|
52
|
+
"resolve_universal",
|
|
53
|
+
]
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
logger = logging.getLogger(__name__)
|
|
57
|
+
|
|
58
|
+
# Cap on the per-tuple ResolutionError message stashed in
|
|
59
|
+
# :class:`TupleResult.error`, so a runaway report message never
|
|
60
|
+
# bloats the failure summary.
|
|
61
|
+
_ERROR_MESSAGE_LIMIT = 200
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
if TYPE_CHECKING:
|
|
65
|
+
from collections.abc import Mapping, Sequence
|
|
66
|
+
from datetime import datetime
|
|
67
|
+
from pathlib import Path
|
|
68
|
+
|
|
69
|
+
from nab_index.transport import AsyncHttpTransport
|
|
70
|
+
|
|
71
|
+
from .._vendor.packaging.version import Version
|
|
72
|
+
from ..config import NabProjectConfig
|
|
73
|
+
from .matrix import Matrix, MatrixTuple
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@dataclass
|
|
77
|
+
class TupleResult:
|
|
78
|
+
"""Result of one specific resolve."""
|
|
79
|
+
|
|
80
|
+
tuple_: MatrixTuple
|
|
81
|
+
success: bool
|
|
82
|
+
pins: dict[str, Version] = field(default_factory=dict)
|
|
83
|
+
error: str | None = None
|
|
84
|
+
decisions: int = 0
|
|
85
|
+
rounds: int = 0
|
|
86
|
+
wall_time: float = 0.0
|
|
87
|
+
lock_input: LockInput | None = None
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
@dataclass
|
|
91
|
+
class UniversalResult:
|
|
92
|
+
"""Merged result across all tuples."""
|
|
93
|
+
|
|
94
|
+
matrix: Matrix
|
|
95
|
+
tuple_results: list[TupleResult] = field(default_factory=list)
|
|
96
|
+
|
|
97
|
+
@property
|
|
98
|
+
def success(self) -> bool:
|
|
99
|
+
"""Return True iff every tuple resolved successfully."""
|
|
100
|
+
return all(tr.success for tr in self.tuple_results)
|
|
101
|
+
|
|
102
|
+
def merged_lock(self) -> dict[str, list[tuple[str, str]]]:
|
|
103
|
+
"""Collapse per-tuple pins into ``{package: [(version, label), ...]}``.
|
|
104
|
+
|
|
105
|
+
Adjacent labels picking the same version stay together; the
|
|
106
|
+
labels are tuple ids, not PEP 508 markers.
|
|
107
|
+
"""
|
|
108
|
+
out: defaultdict[str, list[tuple[str, str]]] = defaultdict(list)
|
|
109
|
+
for tr in self.tuple_results:
|
|
110
|
+
if not tr.success:
|
|
111
|
+
continue
|
|
112
|
+
for pkg, version in tr.pins.items():
|
|
113
|
+
out[pkg].append((str(version), tr.tuple_.label))
|
|
114
|
+
return out
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def merge_universal_lock_inputs(
|
|
118
|
+
result: UniversalResult,
|
|
119
|
+
*,
|
|
120
|
+
requires_python: str | None = None,
|
|
121
|
+
created_by: str = "nab",
|
|
122
|
+
extras: Sequence[str] = (),
|
|
123
|
+
dependency_groups: Sequence[str] = (),
|
|
124
|
+
default_groups: Sequence[str] = (),
|
|
125
|
+
) -> LockInput:
|
|
126
|
+
"""Merge per-tuple :class:`LockInput` objects into one universal lock.
|
|
127
|
+
|
|
128
|
+
Each successful tuple's pins are stored under ``per_tuple_pins``
|
|
129
|
+
keyed by the tuple's ``label``; the matching PEP 508 marker
|
|
130
|
+
expression is recorded in ``tuple_markers``. The downstream pylock
|
|
131
|
+
writer collapses these into one or more ``Package`` entries per
|
|
132
|
+
name with markers attached.
|
|
133
|
+
|
|
134
|
+
Tuples with ``lock_input is None`` (resolution succeeded but the
|
|
135
|
+
artefact set is missing a ``sha256`` somewhere) are skipped, which
|
|
136
|
+
means the resulting lock omits those tuples. Callers that want
|
|
137
|
+
every tuple represented should check ``UniversalResult.success``
|
|
138
|
+
before calling.
|
|
139
|
+
|
|
140
|
+
``dependency_groups`` and ``default_groups`` are recorded at the
|
|
141
|
+
lockfile top level for PEP 735 multi-use locks. Per-package
|
|
142
|
+
group attribution (markers gated by
|
|
143
|
+
``'group-x' in dependency_groups``) is not emitted.
|
|
144
|
+
"""
|
|
145
|
+
per_tuple_pins: dict[str, dict[str, PinShape]] = {}
|
|
146
|
+
tuple_markers: dict[str, Marker] = {}
|
|
147
|
+
tuple_environments: dict[str, dict[str, str]] = {}
|
|
148
|
+
environments: list[Marker] = []
|
|
149
|
+
for tr in result.tuple_results:
|
|
150
|
+
if not tr.success or tr.lock_input is None:
|
|
151
|
+
continue
|
|
152
|
+
label = tr.tuple_.label
|
|
153
|
+
per_tuple_pins[label] = dict(tr.lock_input.pins)
|
|
154
|
+
marker = Marker(tr.tuple_.marker_string)
|
|
155
|
+
tuple_markers[label] = marker
|
|
156
|
+
tuple_environments[label] = dict(tr.tuple_.environment)
|
|
157
|
+
environments.append(marker)
|
|
158
|
+
return LockInput(
|
|
159
|
+
per_tuple_pins=per_tuple_pins,
|
|
160
|
+
tuple_markers=tuple_markers,
|
|
161
|
+
tuple_environments=tuple_environments,
|
|
162
|
+
environments=environments,
|
|
163
|
+
requires_python=requires_python,
|
|
164
|
+
created_by=created_by,
|
|
165
|
+
extras=tuple(extras),
|
|
166
|
+
dependency_groups=tuple(dependency_groups),
|
|
167
|
+
default_groups=tuple(default_groups),
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def resolve_universal( # noqa: PLR0913 - surface area mirrors uv's resolution knobs; bundling all flags into a config object hides the call-site documentation
|
|
172
|
+
matrix: Matrix,
|
|
173
|
+
requirements: list[str],
|
|
174
|
+
*,
|
|
175
|
+
transport: AsyncHttpTransport | None = None,
|
|
176
|
+
offline: bool = False,
|
|
177
|
+
constraints: list[str] | None = None,
|
|
178
|
+
cache_dir: Path | None = None,
|
|
179
|
+
uploaded_prior_to: datetime | None = None,
|
|
180
|
+
uploaded_prior_to_overrides: Mapping[str, datetime | None] | None = None,
|
|
181
|
+
dist_policy: DistPolicy = DistPolicy.WHEEL_OR_SDIST,
|
|
182
|
+
dist_policy_overrides: Mapping[str, DistPolicy] | None = None,
|
|
183
|
+
build_policy: BuildPolicy = BuildPolicy.BUILD_LOCAL,
|
|
184
|
+
build_policy_overrides: Mapping[str, BuildPolicy] | None = None,
|
|
185
|
+
vcs_config: VcsConfig | None = None,
|
|
186
|
+
local_sources: list[LocalSource] | None = None,
|
|
187
|
+
vcs_sources: list[VcsSource] | None = None,
|
|
188
|
+
vcs_cache_dir: Path | None = None,
|
|
189
|
+
build_config: NabProjectConfig | None = None,
|
|
190
|
+
indexes: list[IndexConfig] | None = None,
|
|
191
|
+
index_overrides: list[IndexOverride] | None = None,
|
|
192
|
+
resolution_strategy: str = "highest",
|
|
193
|
+
align_across_tuples: bool = True,
|
|
194
|
+
preferences: dict[str, Version] | None = None,
|
|
195
|
+
) -> UniversalResult:
|
|
196
|
+
"""Run a universal resolve for ``matrix``.
|
|
197
|
+
|
|
198
|
+
Returns a :class:`UniversalResult` with one :class:`TupleResult`
|
|
199
|
+
per (python_version, platform) combination.
|
|
200
|
+
|
|
201
|
+
``resolution_strategy``: ``"highest"`` (default), ``"lowest"``, or
|
|
202
|
+
``"lowest-direct"``. Mirrors uv's ``--resolution`` flag.
|
|
203
|
+
|
|
204
|
+
``align_across_tuples``: when True, after each tuple's resolve we
|
|
205
|
+
accumulate its pins as preferences for subsequent tuples.
|
|
206
|
+
|
|
207
|
+
``preferences``: a starting set of preferred ``{name: Version}``,
|
|
208
|
+
e.g. read from a previous lock.
|
|
209
|
+
"""
|
|
210
|
+
if indexes is None:
|
|
211
|
+
indexes = [IndexConfig(DEFAULT_INDEX_NAME, DEFAULT_INDEX_URL)]
|
|
212
|
+
effective_transport: AsyncHttpTransport = (
|
|
213
|
+
transport if transport is not None else Urllib3AsyncTransport()
|
|
214
|
+
)
|
|
215
|
+
with FetchCoordinator(
|
|
216
|
+
effective_transport,
|
|
217
|
+
indexes=indexes,
|
|
218
|
+
cache_dir=cache_dir,
|
|
219
|
+
offline=offline,
|
|
220
|
+
index_overrides=index_overrides,
|
|
221
|
+
) as coordinator:
|
|
222
|
+
return resolve_with_coordinator(
|
|
223
|
+
coordinator,
|
|
224
|
+
matrix,
|
|
225
|
+
requirements,
|
|
226
|
+
constraints=constraints,
|
|
227
|
+
uploaded_prior_to=uploaded_prior_to,
|
|
228
|
+
uploaded_prior_to_overrides=uploaded_prior_to_overrides,
|
|
229
|
+
dist_policy=dist_policy,
|
|
230
|
+
dist_policy_overrides=dist_policy_overrides,
|
|
231
|
+
build_policy=build_policy,
|
|
232
|
+
build_policy_overrides=build_policy_overrides,
|
|
233
|
+
vcs_config=vcs_config,
|
|
234
|
+
local_sources=local_sources,
|
|
235
|
+
vcs_sources=vcs_sources,
|
|
236
|
+
vcs_cache_dir=vcs_cache_dir,
|
|
237
|
+
build_config=build_config,
|
|
238
|
+
resolution_strategy=resolution_strategy,
|
|
239
|
+
align_across_tuples=align_across_tuples,
|
|
240
|
+
preferences=preferences,
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def resolve_with_coordinator( # noqa: PLR0913 - mirrors resolve_universal's surface
|
|
245
|
+
coordinator: FetchCoordinator,
|
|
246
|
+
matrix: Matrix,
|
|
247
|
+
requirements: list[str],
|
|
248
|
+
*,
|
|
249
|
+
constraints: list[str] | None = None,
|
|
250
|
+
uploaded_prior_to: datetime | None = None,
|
|
251
|
+
uploaded_prior_to_overrides: (Mapping[str, datetime | None] | None) = None,
|
|
252
|
+
dist_policy: DistPolicy = DistPolicy.WHEEL_OR_SDIST,
|
|
253
|
+
dist_policy_overrides: Mapping[str, DistPolicy] | None = None,
|
|
254
|
+
build_policy: BuildPolicy = BuildPolicy.BUILD_LOCAL,
|
|
255
|
+
build_policy_overrides: Mapping[str, BuildPolicy] | None = None,
|
|
256
|
+
vcs_config: VcsConfig | None = None,
|
|
257
|
+
local_sources: list[LocalSource] | None = None,
|
|
258
|
+
vcs_sources: list[VcsSource] | None = None,
|
|
259
|
+
vcs_cache_dir: Path | None = None,
|
|
260
|
+
build_config: NabProjectConfig | None = None,
|
|
261
|
+
resolution_strategy: str = "highest",
|
|
262
|
+
align_across_tuples: bool = True,
|
|
263
|
+
preferences: dict[str, Version] | None = None,
|
|
264
|
+
) -> UniversalResult:
|
|
265
|
+
"""Run a universal resolve against an already-open coordinator.
|
|
266
|
+
|
|
267
|
+
Splitting the entry point lets callers (and tests) reuse a single
|
|
268
|
+
:class:`FetchCoordinator` across multiple resolves and avoids
|
|
269
|
+
transport setup in unit tests. See :func:`resolve_universal` for
|
|
270
|
+
the full parameter documentation.
|
|
271
|
+
"""
|
|
272
|
+
tuples = matrix.expand()
|
|
273
|
+
_warn_extra_marker_at_root(requirements)
|
|
274
|
+
direct_packages = frozenset(_direct_package_names(requirements))
|
|
275
|
+
initial_preferences: dict[str, Version] = dict(preferences or {})
|
|
276
|
+
|
|
277
|
+
return UniversalResult(
|
|
278
|
+
matrix=matrix,
|
|
279
|
+
tuple_results=_run_pass(
|
|
280
|
+
tuples,
|
|
281
|
+
requirements,
|
|
282
|
+
constraints,
|
|
283
|
+
coordinator=coordinator,
|
|
284
|
+
uploaded_prior_to=uploaded_prior_to,
|
|
285
|
+
uploaded_prior_to_overrides=uploaded_prior_to_overrides,
|
|
286
|
+
dist_policy=dist_policy,
|
|
287
|
+
dist_policy_overrides=dist_policy_overrides,
|
|
288
|
+
build_policy=build_policy,
|
|
289
|
+
build_policy_overrides=build_policy_overrides,
|
|
290
|
+
vcs_config=vcs_config,
|
|
291
|
+
local_sources=local_sources,
|
|
292
|
+
vcs_sources=vcs_sources,
|
|
293
|
+
vcs_cache_dir=vcs_cache_dir,
|
|
294
|
+
build_config=build_config,
|
|
295
|
+
resolution_strategy=resolution_strategy,
|
|
296
|
+
direct_packages=direct_packages,
|
|
297
|
+
preferences=initial_preferences,
|
|
298
|
+
align_serial=align_across_tuples,
|
|
299
|
+
),
|
|
300
|
+
)
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
def _run_pass( # noqa: PLR0913
|
|
304
|
+
tuples: list[MatrixTuple],
|
|
305
|
+
requirements: list[str],
|
|
306
|
+
constraints: list[str] | None,
|
|
307
|
+
*,
|
|
308
|
+
coordinator: FetchCoordinator,
|
|
309
|
+
uploaded_prior_to: datetime | None,
|
|
310
|
+
uploaded_prior_to_overrides: Mapping[str, datetime | None] | None = None,
|
|
311
|
+
dist_policy: DistPolicy,
|
|
312
|
+
dist_policy_overrides: Mapping[str, DistPolicy] | None = None,
|
|
313
|
+
build_policy: BuildPolicy,
|
|
314
|
+
build_policy_overrides: Mapping[str, BuildPolicy] | None = None,
|
|
315
|
+
vcs_config: VcsConfig | None = None,
|
|
316
|
+
local_sources: list[LocalSource] | None = None,
|
|
317
|
+
vcs_sources: list[VcsSource] | None = None,
|
|
318
|
+
vcs_cache_dir: Path | None = None,
|
|
319
|
+
build_config: NabProjectConfig | None = None,
|
|
320
|
+
resolution_strategy: str,
|
|
321
|
+
direct_packages: frozenset[str],
|
|
322
|
+
preferences: dict[str, Version],
|
|
323
|
+
align_serial: bool,
|
|
324
|
+
) -> list[TupleResult]:
|
|
325
|
+
"""Run one serial pass of resolution across ``tuples``.
|
|
326
|
+
|
|
327
|
+
When ``align_serial=True``, each tuple's pins are threaded forward
|
|
328
|
+
as preferences for the next so the per-Python pins stay aligned
|
|
329
|
+
where the matrix admits it.
|
|
330
|
+
"""
|
|
331
|
+
|
|
332
|
+
def resolve(t: MatrixTuple, current_prefs: dict[str, Version]) -> TupleResult:
|
|
333
|
+
tuple_requirements = _parse_requirements(requirements, t.environment)
|
|
334
|
+
tuple_constraints = (
|
|
335
|
+
_parse_requirements(constraints, t.environment) if constraints else None
|
|
336
|
+
)
|
|
337
|
+
return _resolve_one_tuple(
|
|
338
|
+
coordinator,
|
|
339
|
+
t,
|
|
340
|
+
tuple_requirements,
|
|
341
|
+
tuple_constraints,
|
|
342
|
+
uploaded_prior_to=uploaded_prior_to,
|
|
343
|
+
uploaded_prior_to_overrides=uploaded_prior_to_overrides,
|
|
344
|
+
dist_policy=dist_policy,
|
|
345
|
+
dist_policy_overrides=dist_policy_overrides,
|
|
346
|
+
build_policy=build_policy,
|
|
347
|
+
build_policy_overrides=build_policy_overrides,
|
|
348
|
+
vcs_config=vcs_config,
|
|
349
|
+
local_sources=local_sources,
|
|
350
|
+
vcs_sources=vcs_sources,
|
|
351
|
+
vcs_cache_dir=vcs_cache_dir,
|
|
352
|
+
build_config=build_config,
|
|
353
|
+
resolution_strategy=resolution_strategy,
|
|
354
|
+
preferences=dict(current_prefs),
|
|
355
|
+
direct_packages=direct_packages,
|
|
356
|
+
)
|
|
357
|
+
|
|
358
|
+
out: list[TupleResult] = []
|
|
359
|
+
accumulated = dict(preferences)
|
|
360
|
+
for t in tuples:
|
|
361
|
+
tr = resolve(t, accumulated)
|
|
362
|
+
out.append(tr)
|
|
363
|
+
if align_serial and tr.success:
|
|
364
|
+
accumulated.update(tr.pins)
|
|
365
|
+
return out
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
def _warn_extra_marker_at_root(requirements: list[str]) -> list[str]:
|
|
369
|
+
"""Warn when a root requirement uses an ``extra ==`` marker.
|
|
370
|
+
|
|
371
|
+
Hole 1.6 plug. ``packaging`` defaults the ``extra`` variable to
|
|
372
|
+
``""`` at root, so a marker like ``pkg ; extra == "test"`` evaluates
|
|
373
|
+
False during root parsing and the dep is silently dropped. The
|
|
374
|
+
user almost certainly meant the ``parent[test]`` extra-of-parent
|
|
375
|
+
syntax, not a self-referential extra marker.
|
|
376
|
+
|
|
377
|
+
Returns the list of requirement strings that triggered the warning
|
|
378
|
+
so callers can write tests against the diagnostic without parsing
|
|
379
|
+
the log output.
|
|
380
|
+
"""
|
|
381
|
+
flagged: list[str] = []
|
|
382
|
+
for req_str in requirements:
|
|
383
|
+
try:
|
|
384
|
+
req = Requirement(req_str)
|
|
385
|
+
except InvalidRequirement: # pragma: no cover - re-raised at resolve time
|
|
386
|
+
continue
|
|
387
|
+
marker_text = str(req.marker or "")
|
|
388
|
+
if "extra ==" in marker_text or "extra==" in marker_text:
|
|
389
|
+
flagged.append(req_str)
|
|
390
|
+
logger.warning(
|
|
391
|
+
"Root requirement %r uses an ``extra`` marker; the dep will be "
|
|
392
|
+
"silently dropped because root has no parent extra. Did you "
|
|
393
|
+
"mean ``pkg[extra]`` (extras-of-package) instead?",
|
|
394
|
+
req_str,
|
|
395
|
+
)
|
|
396
|
+
return flagged
|
|
397
|
+
|
|
398
|
+
|
|
399
|
+
def _direct_package_names(requirements: list[str]) -> set[str]:
|
|
400
|
+
"""Return the canonical names of the user's direct dependencies.
|
|
401
|
+
|
|
402
|
+
Malformed requirement strings raise downstream during the actual
|
|
403
|
+
resolve; here we just collect names for the lowest-direct strategy
|
|
404
|
+
and let them surface naturally.
|
|
405
|
+
"""
|
|
406
|
+
return {canonicalize_name(Requirement(req_str).name) for req_str in requirements}
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
def _parse_requirements(
|
|
410
|
+
reqs: list[str],
|
|
411
|
+
environment: dict[str, str],
|
|
412
|
+
) -> dict[str, VersionRange]:
|
|
413
|
+
"""Convert PEP 508 strings to resolver requirements for ``environment``.
|
|
414
|
+
|
|
415
|
+
Marker-gated requirements whose marker evaluates to False in
|
|
416
|
+
``environment`` are dropped. This matches what
|
|
417
|
+
``Provider._classify_requirement`` does for transitive deps;
|
|
418
|
+
we just apply the same rule at the root.
|
|
419
|
+
"""
|
|
420
|
+
out: dict[str, VersionRange] = {}
|
|
421
|
+
for req_str in reqs:
|
|
422
|
+
req = Requirement(req_str)
|
|
423
|
+
if req.marker is not None and not req.marker.evaluate(environment):
|
|
424
|
+
continue
|
|
425
|
+
name = canonicalize_name(req.name)
|
|
426
|
+
out[name] = req.specifier.to_range()
|
|
427
|
+
for extra in req.extras:
|
|
428
|
+
out[f"{name}[{extra}]"] = VersionRange.full()
|
|
429
|
+
return out
|
|
430
|
+
|
|
431
|
+
|
|
432
|
+
def _resolve_one_tuple( # noqa: PLR0913
|
|
433
|
+
coordinator: FetchCoordinator,
|
|
434
|
+
t: MatrixTuple,
|
|
435
|
+
requirements: dict[str, VersionRange],
|
|
436
|
+
constraints: dict[str, VersionRange] | None,
|
|
437
|
+
*,
|
|
438
|
+
uploaded_prior_to: datetime | None,
|
|
439
|
+
uploaded_prior_to_overrides: Mapping[str, datetime | None] | None = None,
|
|
440
|
+
dist_policy: DistPolicy,
|
|
441
|
+
dist_policy_overrides: Mapping[str, DistPolicy] | None = None,
|
|
442
|
+
build_policy: BuildPolicy,
|
|
443
|
+
build_policy_overrides: Mapping[str, BuildPolicy] | None = None,
|
|
444
|
+
vcs_config: VcsConfig | None = None,
|
|
445
|
+
local_sources: list[LocalSource] | None = None,
|
|
446
|
+
vcs_sources: list[VcsSource] | None = None,
|
|
447
|
+
vcs_cache_dir: Path | None = None,
|
|
448
|
+
build_config: NabProjectConfig | None = None,
|
|
449
|
+
resolution_strategy: str = "highest",
|
|
450
|
+
preferences: dict[str, Version] | None = None,
|
|
451
|
+
direct_packages: frozenset[str] = frozenset(),
|
|
452
|
+
) -> TupleResult:
|
|
453
|
+
"""Run one single-environment resolve for ``t``."""
|
|
454
|
+
provider = UniversalProvider(
|
|
455
|
+
coordinator,
|
|
456
|
+
marker_environment=t.environment,
|
|
457
|
+
root_requirements=requirements,
|
|
458
|
+
uploaded_prior_to=uploaded_prior_to,
|
|
459
|
+
uploaded_prior_to_overrides=uploaded_prior_to_overrides,
|
|
460
|
+
dist_policy=dist_policy,
|
|
461
|
+
dist_policy_overrides=dist_policy_overrides,
|
|
462
|
+
build_policy_overrides=build_policy_overrides,
|
|
463
|
+
vcs_config=vcs_config,
|
|
464
|
+
local_sources=local_sources,
|
|
465
|
+
vcs_sources=vcs_sources,
|
|
466
|
+
vcs_cache_dir=vcs_cache_dir,
|
|
467
|
+
build_config=build_config,
|
|
468
|
+
build_policy=build_policy,
|
|
469
|
+
preferences=preferences,
|
|
470
|
+
resolution_strategy=resolution_strategy,
|
|
471
|
+
direct_packages=direct_packages,
|
|
472
|
+
platform_spec=t.platform_spec,
|
|
473
|
+
)
|
|
474
|
+
resolver: Resolver[str, Version] = Resolver(
|
|
475
|
+
provider,
|
|
476
|
+
range_type=VersionRange,
|
|
477
|
+
root_version="0",
|
|
478
|
+
max_iterations=50_000,
|
|
479
|
+
)
|
|
480
|
+
start = time.monotonic()
|
|
481
|
+
try:
|
|
482
|
+
raw = resolver.resolve(requirements, constraints=constraints)
|
|
483
|
+
except ResolutionError as exc:
|
|
484
|
+
return TupleResult(
|
|
485
|
+
tuple_=t,
|
|
486
|
+
success=False,
|
|
487
|
+
error=f"{type(exc).__name__}: {exc}"[:_ERROR_MESSAGE_LIMIT],
|
|
488
|
+
wall_time=time.monotonic() - start,
|
|
489
|
+
rounds=resolver.stats.rounds,
|
|
490
|
+
decisions=resolver.stats.decisions,
|
|
491
|
+
)
|
|
492
|
+
elapsed = time.monotonic() - start
|
|
493
|
+
pins = {k: v for k, v in raw.items() if split_extra(k)[1] is None}
|
|
494
|
+
try:
|
|
495
|
+
lock_input = build_lock_input_from_provider(
|
|
496
|
+
provider, pins, indexes=coordinator.indexes
|
|
497
|
+
)
|
|
498
|
+
except MissingHashError:
|
|
499
|
+
lock_input = None
|
|
500
|
+
return TupleResult(
|
|
501
|
+
tuple_=t,
|
|
502
|
+
success=True,
|
|
503
|
+
pins=pins,
|
|
504
|
+
wall_time=elapsed,
|
|
505
|
+
rounds=resolver.stats.rounds,
|
|
506
|
+
decisions=resolver.stats.decisions,
|
|
507
|
+
lock_input=lock_input,
|
|
508
|
+
)
|