Skip to main content

uv_resolver/resolver/
mod.rs

1//! Given a set of requirements, find a set of compatible packages.
2
3use std::borrow::Cow;
4use std::cmp::Ordering;
5use std::collections::{BTreeMap, BTreeSet, VecDeque};
6use std::fmt::{Display, Formatter};
7use std::ops::Bound;
8use std::sync::Arc;
9use std::time::Instant;
10use std::{mem, thread};
11
12use futures::{FutureExt, StreamExt};
13use itertools::Itertools;
14use papaya::{HashMap, ResizeMode};
15use pubgrub::{Id, IncompId, Incompatibility, Kind, Ranges, State, Term};
16use rustc_hash::{FxHashMap, FxHashSet};
17use tokio::sync::mpsc::{self, Receiver};
18use tokio::sync::oneshot;
19use tokio_stream::wrappers::ReceiverStream;
20use tracing::{Level, debug, info, instrument, trace, warn};
21
22use uv_configuration::{Constraints, DependencyModifiers};
23use uv_distribution::{ArchiveMetadata, DistributionDatabase};
24use uv_distribution_types::{
25    BuiltDist, DerivationChain, Dist, DistErrorKind, Identifier, IndexCapabilities, IndexLocations,
26    IndexMetadata, IndexUrl, InstalledDist, Name, RemoteSource, Requirement, RequiresPython,
27    ResolutionRecorder, ResolvedDist, SourceDist, VersionOrUrlRef,
28};
29use uv_git::GitResolver;
30use uv_normalize::PackageName;
31use uv_pep440::{MIN_VERSION, Version, VersionSpecifiers, release_specifiers_to_ranges};
32use uv_pep508::{
33    MarkerEnvironment, MarkerExpression, MarkerOperator, MarkerTree, MarkerValueString,
34};
35use uv_platform_tags::{IncompatibleTag, Tags};
36use uv_preview::PreviewFeature;
37use uv_pypi_types::{ConflictItem, ConflictItemRef, ConflictKindRef, Conflicts, VerbatimParsedUrl};
38use uv_resolver_types::PackageNodeKind;
39use uv_static::EnvVars;
40use uv_torch::TorchStrategy;
41use uv_types::{BuildContext, HashStrategy, InstalledPackagesProvider};
42use uv_warnings::warn_user_once;
43
44use crate::candidate_selector::{Candidate, CandidateDist, CandidateSelector};
45use crate::dependency_provider::UvDependencyProvider;
46use crate::error::{NoSolutionError, ResolveError, derivation_tree_packages};
47use crate::fork_indexes::ForkIndexes;
48use crate::fork_urls::ForkUrls;
49use crate::manifest::Manifest;
50use crate::pins::FilePins;
51use crate::preferences::{PreferenceSource, Preferences};
52use crate::prioritized_distribution::{
53    CompatibleDist, IncompatibleDist, IncompatibleSource, IncompatibleWheel, PythonRequirementKind,
54    RegistryVariantsJson, implied_markers,
55};
56use crate::pubgrub::{
57    DependencySource, PubGrubDependency, PubGrubPackage, PubGrubPackageInner, PubGrubPriorities,
58    PubGrubPython, Range,
59};
60use crate::python_requirement::PythonRequirement;
61use crate::resolution::ResolverOutput;
62use crate::resolution_mode::ResolutionStrategy;
63use crate::resolved::ResolvedDistRef;
64pub(crate) use crate::resolver::availability::{
65    ResolverVersion, UnavailablePackage, UnavailableReason, UnavailableVersion,
66    UnsatisfiableRequirement,
67};
68use crate::resolver::batch_prefetch::BatchPrefetcher;
69use crate::resolver::derivation::DerivationChainBuilder;
70pub use crate::resolver::environment::ResolverEnvironment;
71use crate::resolver::environment::{
72    ForkingPossibility, fork_version_by_marker, fork_version_by_python_requirement,
73};
74pub(crate) use crate::resolver::fork_map::{ForkMap, ForkSet};
75pub use crate::resolver::index::InMemoryIndex;
76use crate::resolver::indexes::Indexes;
77use crate::resolver::package_source::PackageSource;
78pub use crate::resolver::provider::{
79    DefaultResolverProvider, MetadataResponse, PackageVersionsResult, ResolverProvider,
80    VariantsJsonResponse, VersionsResponse, WheelMetadataResult,
81};
82pub use crate::resolver::reporter::Reporter;
83use crate::resolver::requests::{MetadataRequest, MetadataRequests};
84pub(crate) use crate::resolver::requests::{RegisteredMetadata, RegisteredVariantsJson};
85use crate::resolver::requirements::{RequirementContext, RequirementExpander};
86use crate::resolver::system::SystemDependency;
87pub(crate) use crate::resolver::urls::Urls;
88use crate::universal_marker::UniversalMarker;
89use crate::yanks::AllowedYanks;
90use crate::{DependencyMode, Exclusions, FlatIndex, Options, ResolutionMode, VersionMap, marker};
91pub(crate) use provider::MetadataUnavailable;
92pub(crate) use resolution::{
93    Resolution, ResolutionDependencyEdge, ResolutionNode, ResolutionNodeId, ResolutionPackage,
94    ResolvedFork, SelectedDistribution,
95};
96use uv_configuration::ForkStrategy;
97
98mod availability;
99mod batch_prefetch;
100mod derivation;
101mod environment;
102mod fork_map;
103mod index;
104mod indexes;
105mod package_source;
106mod provider;
107mod reporter;
108mod requests;
109mod requirements;
110mod resolution;
111mod system;
112mod urls;
113
114/// The number of conflicts a package may accumulate before we re-prioritize and backtrack.
115const CONFLICT_THRESHOLD: usize = 5;
116
117pub struct Resolver<Provider: ResolverProvider, InstalledPackages: InstalledPackagesProvider> {
118    state: ResolverState<InstalledPackages>,
119    provider: Provider,
120}
121
122/// State that is shared between the prefetcher and the PubGrub solver during
123/// resolution, across all forks.
124struct ResolverState<InstalledPackages: InstalledPackagesProvider> {
125    recorder: Option<ResolutionRecorder>,
126    project: Option<PackageName>,
127    requirements: Vec<Requirement>,
128    constraints: Constraints,
129    modifiers: DependencyModifiers,
130    preferences: Preferences,
131    git: GitResolver,
132    capabilities: IndexCapabilities,
133    locations: IndexLocations,
134    exclusions: Exclusions,
135    urls: Urls,
136    indexes: Indexes,
137    dependency_mode: DependencyMode,
138    hasher: HashStrategy,
139    env: ResolverEnvironment,
140    // The environment of the current Python interpreter.
141    current_environment: MarkerEnvironment,
142    tags: Option<Tags>,
143    python_requirement: PythonRequirement,
144    conflicts: Conflicts,
145    workspace_members: BTreeSet<PackageName>,
146    selector: CandidateSelector,
147    index: InMemoryIndex,
148    installed_packages: InstalledPackages,
149    // Papaya's maps are large on Windows, so box them to keep resolver futures small.
150    /// Incompatibilities for packages that are entirely unavailable.
151    unavailable_packages: Box<HashMap<PackageName, UnavailablePackage>>,
152    /// Incompatibilities for packages that are unavailable at specific versions.
153    incomplete_packages: Box<HashMap<PackageName, HashMap<Version, MetadataUnavailable>>>,
154    /// The options that were used to configure this resolver.
155    options: Options,
156    /// The reporter to use for this resolver.
157    reporter: Option<Arc<dyn Reporter>>,
158}
159
160impl<'a, Context: BuildContext, InstalledPackages: InstalledPackagesProvider>
161    Resolver<DefaultResolverProvider<'a, Context>, InstalledPackages>
162{
163    /// Initialize a new resolver using the default backend doing real requests.
164    ///
165    /// Reads the flat index entries.
166    ///
167    /// # Marker environment
168    ///
169    /// The marker environment is optional.
170    ///
171    /// When a marker environment is not provided, the resolver is said to be
172    /// in "universal" mode. When in universal mode, the resolution produced
173    /// may contain multiple versions of the same package. And thus, in order
174    /// to use the resulting resolution, there must be a "universal"-aware
175    /// reader of the resolution that knows to exclude distributions that can't
176    /// be used in the current environment.
177    ///
178    /// When a marker environment is provided, the resolver is in
179    /// "non-universal" mode, which corresponds to standard `pip` behavior that
180    /// works only for a specific marker environment.
181    pub fn new(
182        manifest: Manifest,
183        options: Options,
184        python_requirement: &'a PythonRequirement,
185        env: ResolverEnvironment,
186        current_environment: &MarkerEnvironment,
187        conflicts: Conflicts,
188        tags: Option<&'a Tags>,
189        flat_index: &'a FlatIndex,
190        index: &'a InMemoryIndex,
191        hasher: &'a HashStrategy,
192        build_context: &'a Context,
193        installed_packages: InstalledPackages,
194        database: DistributionDatabase<'a, Context>,
195    ) -> Result<Self, ResolveError> {
196        let provider = DefaultResolverProvider::new(
197            database,
198            flat_index,
199            tags,
200            python_requirement.target(),
201            AllowedYanks::from_manifest(&manifest, &env, options.dependency_mode),
202            hasher,
203            options.exclude_newer.clone(),
204            build_context.locations(),
205            build_context.build_options(),
206            build_context.capabilities(),
207            options.minimum_libc_version,
208        );
209
210        Ok(Self::new_custom_io(
211            manifest,
212            options,
213            hasher,
214            env,
215            current_environment,
216            tags.cloned(),
217            python_requirement,
218            conflicts,
219            index,
220            build_context.git(),
221            build_context.capabilities(),
222            build_context.locations(),
223            provider,
224            installed_packages,
225        ))
226    }
227}
228
229impl<Provider: ResolverProvider, InstalledPackages: InstalledPackagesProvider>
230    Resolver<Provider, InstalledPackages>
231{
232    /// Initialize a new resolver using a user provided backend.
233    pub fn new_custom_io(
234        manifest: Manifest,
235        options: Options,
236        hasher: &HashStrategy,
237        env: ResolverEnvironment,
238        current_environment: &MarkerEnvironment,
239        tags: Option<Tags>,
240        python_requirement: &PythonRequirement,
241        conflicts: Conflicts,
242        index: &InMemoryIndex,
243        git: &GitResolver,
244        capabilities: &IndexCapabilities,
245        locations: &IndexLocations,
246        provider: Provider,
247        installed_packages: InstalledPackages,
248    ) -> Self {
249        let state = ResolverState {
250            index: index.clone(),
251            git: git.clone(),
252            capabilities: capabilities.clone(),
253            selector: CandidateSelector::for_resolution(&options, &manifest, &env),
254            dependency_mode: options.dependency_mode,
255            urls: Urls::from_manifest(&manifest, &env, git, options.dependency_mode),
256            indexes: Indexes::from_manifest(&manifest, &env, options.dependency_mode),
257            recorder: manifest.recorder.clone(),
258            project: manifest.project,
259            workspace_members: manifest.workspace_members.into_keys().collect(),
260            requirements: manifest.requirements,
261            constraints: manifest
262                .constraints
263                .with_recorder(manifest.recorder.clone()),
264            modifiers: manifest.modifiers.with_recorder(manifest.recorder.clone()),
265            preferences: manifest.preferences,
266            exclusions: manifest.exclusions,
267            hasher: hasher.clone(),
268            locations: locations.clone(),
269            env,
270            current_environment: current_environment.clone(),
271            tags,
272            python_requirement: python_requirement.clone(),
273            conflicts,
274            installed_packages,
275            unavailable_packages: Box::default(),
276            incomplete_packages: Box::default(),
277            options,
278            reporter: None,
279        };
280        Self { state, provider }
281    }
282
283    /// Set the [`Reporter`] to use for this installer.
284    #[must_use]
285    pub fn with_reporter(self, reporter: Arc<dyn Reporter>) -> Self {
286        Self {
287            state: ResolverState {
288                reporter: Some(reporter.clone()),
289                ..self.state
290            },
291            provider: self
292                .provider
293                .with_reporter(reporter.into_distribution_reporter()),
294        }
295    }
296
297    /// Resolve a set of requirements into a set of pinned versions.
298    pub async fn resolve(self) -> Result<ResolverOutput, ResolveError> {
299        let state = Arc::new(self.state);
300        let provider = Arc::new(self.provider);
301
302        // A channel to fetch package metadata (e.g., given `flask`, fetch all versions) and version
303        // metadata (e.g., given `flask==1.0.0`, fetch the metadata for that version).
304        // Channel size is set large to accommodate batch prefetching.
305        let (request_sink, request_stream) = mpsc::channel(300);
306        let requests =
307            MetadataRequests::new(state.index.clone(), request_sink, state.recorder.clone());
308
309        // Run the fetcher.
310        let requests_fut = state.clone().fetch(provider.clone(), request_stream).fuse();
311
312        // Spawn the PubGrub solver on a dedicated thread.
313        let solver = state.clone();
314        let (tx, rx) = oneshot::channel();
315        thread::Builder::new()
316            .name("uv-resolver".into())
317            .spawn(move || {
318                let result = solver.solve(&requests);
319
320                // This may fail if the main thread returned early due to an error.
321                let _ = tx.send(result);
322            })
323            .unwrap();
324
325        let resolve_fut = async move { rx.await.map_err(|_| ResolveError::ChannelClosed) };
326
327        // Wait for both to complete.
328        let ((), resolution) = tokio::try_join!(requests_fut, resolve_fut)?;
329
330        state.on_complete();
331        resolution
332    }
333}
334
335impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackages> {
336    #[instrument(skip_all)]
337    fn solve(self: Arc<Self>, requests: &MetadataRequests) -> Result<ResolverOutput, ResolveError> {
338        debug!(
339            "Solving with installed Python version: {}",
340            self.python_requirement.exact()
341        );
342        debug!(
343            "Solving with target Python version: {}",
344            self.python_requirement.target()
345        );
346        if !self.options.exclude_newer.is_empty() {
347            debug!("Solving with exclude-newer: {}", self.options.exclude_newer);
348        }
349
350        let mut visited = FxHashSet::default();
351
352        let root = PubGrubPackage::from(PubGrubPackageInner::Root(self.project.clone()));
353        let pubgrub = State::init(root.clone(), MIN_VERSION.clone());
354        let prefetcher = BatchPrefetcher::new(self.capabilities.clone(), requests.clone());
355        let state = ForkState::new(
356            pubgrub,
357            self.env.clone(),
358            self.python_requirement.clone(),
359            prefetcher,
360        );
361        let mut preferences = self.preferences.clone();
362        let mut forked_states = self.env.initial_forked_states(state)?;
363
364        // Apply the same Python-bound scheduling used for dependency-created forks. Since states
365        // are popped from the end of the stack, sort lower Python bounds last for `fewest` and
366        // higher Python bounds last for `requires-python`. There's no `cmp_upper_bounds` tiebreak
367        // here: it counts upper-bounded specifiers among a fork's dependencies, which an initial
368        // state doesn't have yet.
369        match (self.options.fork_strategy, self.options.resolution_mode) {
370            (ForkStrategy::Fewest, _) | (_, ResolutionMode::Lowest) => {
371                forked_states.sort_by(|a, b| cmp_requires_python(&a.env, &b.env).reverse());
372            }
373            (ForkStrategy::RequiresPython, _) => {
374                forked_states.sort_by(|a, b| cmp_requires_python(&a.env, &b.env));
375            }
376        }
377        let mut resolutions = vec![];
378
379        'FORK: while let Some(mut state) = forked_states.pop() {
380            if let Some(split) = state.env.end_user_fork_display() {
381                let requires_python = state.python_requirement.target();
382                debug!("Solving {split} (requires-python: {requires_python:?})");
383            }
384            let start = Instant::now();
385            loop {
386                let continuation = mem::take(&mut state.continuation);
387                let (highest_priority_pkg, initial_version) = match continuation {
388                    ForkContinuation::SelectVersion { package } => (package, None),
389                    ForkContinuation::UseVersion { package, version } => (package, Some(version)),
390                    ForkContinuation::Propagate => {
391                        // Run unit propagation.
392                        let result = state.pubgrub.unit_propagation(state.next);
393                        match result {
394                            Err(err) => {
395                                // If unit propagation failed, there is no solution.
396                                return Err(self.convert_no_solution_err(
397                                    err,
398                                    state.fork_urls,
399                                    state.fork_indexes,
400                                    &state.known_versions.0,
401                                    state.env,
402                                    self.current_environment.clone(),
403                                    &visited,
404                                ));
405                            }
406                            Ok(conflicts) => {
407                                for (affected, incompatibility) in conflicts {
408                                    // Conflict tracking: If there was a conflict, track affected and
409                                    // culprit for all root cause incompatibilities
410                                    state.record_conflict(affected, None, incompatibility);
411                                }
412                            }
413                        }
414
415                        // Pre-visit all candidate packages, to allow metadata to be fetched in parallel.
416                        if self.dependency_mode.is_transitive() {
417                            Self::pre_visit(
418                                state.pubgrub.partial_solution.prioritized_packages().map(
419                                    |(id, range)| (id, &state.pubgrub.package_store[id], range),
420                                ),
421                                &mut state.pre_visited,
422                                &self.urls,
423                                &self.indexes,
424                                &state.python_requirement,
425                                requests,
426                            )?;
427                        }
428
429                        state.reprioritize_conflicts();
430
431                        trace!(
432                            "Assigned packages: {}",
433                            state
434                                .pubgrub
435                                .partial_solution
436                                .extract_solution()
437                                .filter(|(p, _)| !state.pubgrub.package_store[*p].is_proxy())
438                                .map(|(p, v)| format!("{}=={}", state.pubgrub.package_store[p], v))
439                                .join(", ")
440                        );
441                        // Choose a package.
442                        // We aren't allowed to use the term intersection as it would extend the
443                        // mutable borrow of `state`.
444                        let Some((highest_priority_pkg, _)) =
445                            state.pubgrub.partial_solution.pick_highest_priority_pkg(
446                                |id, _range| state.priorities.get(&state.pubgrub.package_store[id]),
447                            )
448                        else {
449                            // All packages have been assigned, the fork has been successfully resolved
450                            if tracing::enabled!(Level::DEBUG) {
451                                state.prefetcher.log_tried_versions();
452                            }
453                            debug!(
454                                "{} resolution took {:.3}s",
455                                state.env,
456                                start.elapsed().as_secs_f32()
457                            );
458
459                            let resolution = state.into_resolution();
460
461                            // Walk over the selected versions, and mark them as preferences. We have to
462                            // add forks back as to not override the preferences from the lockfile for
463                            // the next fork
464                            //
465                            // If we're using a resolution mode that varies based on whether a dependency is
466                            // direct or transitive, skip preferences, as we risk adding a preference from
467                            // one fork (in which it's a transitive dependency) to another fork (in which
468                            // it's direct).
469                            if matches!(
470                                self.options.resolution_mode,
471                                ResolutionMode::Lowest | ResolutionMode::Highest
472                            ) {
473                                let marker = resolution
474                                    .env
475                                    .try_universal_markers()
476                                    .unwrap_or(UniversalMarker::TRUE);
477                                for (package, version) in &resolution.nodes {
478                                    preferences.insert(
479                                        package.name.clone(),
480                                        package.index.clone(),
481                                        marker,
482                                        version.clone(),
483                                        PreferenceSource::Resolver,
484                                    );
485                                }
486                            }
487
488                            resolutions.push(resolution);
489                            continue 'FORK;
490                        };
491                        trace!(
492                            "Chose package for decision: {}. remaining choices: {}",
493                            state.pubgrub.package_store[highest_priority_pkg],
494                            state
495                                .pubgrub
496                                .partial_solution
497                                .undecided_packages()
498                                .filter(|(p, _)| !state.pubgrub.package_store[**p].is_proxy())
499                                .map(|(p, _)| state.pubgrub.package_store[*p].to_string())
500                                .join(", ")
501                        );
502
503                        (highest_priority_pkg, None)
504                    }
505                };
506
507                state.next = highest_priority_pkg;
508
509                // TODO(charlie): Remove as many usages of `next_package` as we can.
510                let next_id = state.next;
511                let next_package = &state.pubgrub.package_store[state.next];
512
513                let source =
514                    PackageSource::from_fork(next_package, &state.fork_urls, &state.fork_indexes);
515
516                // Consider:
517                // ```toml
518                // dependencies = [
519                //   "iniconfig == 1.1.1 ; python_version < '3.12'",
520                //   "iniconfig @ https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl ; python_version >= '3.12'",
521                // ]
522                // ```
523                // In the `python_version < '3.12'` case, we haven't pre-visited `iniconfig` yet,
524                // since we weren't sure whether it might also be a URL requirement when
525                // transforming the requirements. For that case, we do another request here
526                // (idempotent due to caching).
527                self.request_package(next_package, source, requests)?;
528
529                let version = if let Some(version) = initial_version {
530                    version
531                } else {
532                    let term_intersection = state
533                        .pubgrub
534                        .partial_solution
535                        .term_intersection_for_package(next_id)
536                        .expect("a package was chosen but we don't have a term");
537                    let range = term_intersection.unwrap_positive();
538
539                    // Within a fixed resolver environment, an implicit registry candidate is
540                    // stable for a given range and pre-release policy. Avoid repeating candidate
541                    // selection when PubGrub revisits an identical decision after backtracking.
542                    let cache_selected_version = match source {
543                        PackageSource::Registry(None) => true,
544                        PackageSource::Url(_) | PackageSource::Registry(Some(_)) => false,
545                    };
546                    let decision = if cache_selected_version
547                        && let Some((selected_range, version)) =
548                            state.selected_versions.get(&next_id)
549                        && selected_range == range
550                    {
551                        Some(ResolverVersion::Unforked(version.clone()))
552                    } else {
553                        let decision = self.choose_version(
554                            next_package,
555                            next_id,
556                            source,
557                            range,
558                            &mut state.pins,
559                            &preferences,
560                            &state.env,
561                            &state.python_requirement,
562                            &state.pubgrub,
563                            &mut visited,
564                            requests,
565                        )?;
566
567                        if cache_selected_version
568                            && let Some(ResolverVersion::Unforked(version)) = &decision
569                        {
570                            state
571                                .selected_versions
572                                .insert(next_id, (range.clone(), version.clone()));
573                        }
574
575                        decision
576                    };
577
578                    // Pick the next compatible version.
579                    let Some(version) = decision else {
580                        debug!("No compatible version found for: {next_package}");
581
582                        let term_intersection = state
583                            .pubgrub
584                            .partial_solution
585                            .term_intersection_for_package(next_id)
586                            .expect("a package was chosen but we don't have a term");
587
588                        if let PubGrubPackageInner::Package { name, .. } = &**next_package {
589                            // Check if the decision was due to the package being unavailable
590                            if let Some(reason) = self.unavailable_packages.pin().get(name) {
591                                state
592                                    .pubgrub
593                                    .add_incompatibility(Incompatibility::custom_term(
594                                        next_id,
595                                        term_intersection.clone(),
596                                        UnavailableReason::Package(reason.clone()),
597                                    ));
598                                continue;
599                            }
600                        }
601
602                        state
603                            .pubgrub
604                            .add_incompatibility(Incompatibility::no_versions(
605                                next_id,
606                                term_intersection.clone(),
607                            ));
608                        continue;
609                    };
610
611                    let version = match version {
612                        ResolverVersion::Unforked(version) => version,
613                        ResolverVersion::Forked(forks) => {
614                            forked_states.extend(self.version_forks_to_fork_states(state, forks));
615                            continue 'FORK;
616                        }
617                        ResolverVersion::Unavailable(version, reason) => {
618                            state.add_unavailable_version(
619                                version,
620                                reason,
621                                &self.index,
622                                &self.installed_packages,
623                            );
624                            continue;
625                        }
626                    };
627
628                    // Only consider registry packages for prefetch.
629                    if let PackageSource::Registry(index) = source {
630                        state.prefetcher.prefetch_batches(
631                            next_package,
632                            index,
633                            &version,
634                            term_intersection.unwrap_positive(),
635                            state
636                                .pubgrub
637                                .partial_solution
638                                .unchanging_term_for_package(next_id),
639                            &state.python_requirement,
640                            &self.selector,
641                            &state.env,
642                        )?;
643                    }
644
645                    version
646                };
647
648                state.prefetcher.version_tried(next_package, &version);
649
650                self.on_progress(next_package, &version);
651
652                if state
653                    .added_dependencies
654                    .get(&next_id)
655                    .is_some_and(|versions| versions.contains(&version))
656                {
657                    // `dep_incompats` are already in `incompatibilities` so we know there are not satisfied
658                    // terms and can add the decision directly.
659                    state
660                        .pubgrub
661                        .partial_solution
662                        .add_decision(next_id, version);
663                    continue;
664                }
665
666                // Retrieve that package dependencies.
667                let forked_deps = self.get_dependencies_forking(
668                    next_id,
669                    next_package,
670                    &version,
671                    &state.pins,
672                    &state.env,
673                    &state.python_requirement,
674                    &state.pubgrub,
675                )?;
676
677                match forked_deps {
678                    ForkedDependencies::Unavailable(reason) => {
679                        // Then here, if we get a reason that we consider unrecoverable, we should
680                        // show the derivation chain.
681                        let versions = state.widen_version_to_gap(
682                            &version,
683                            &self.index,
684                            &self.installed_packages,
685                        );
686                        state
687                            .pubgrub
688                            .add_incompatibility(Incompatibility::custom_term(
689                                next_id,
690                                Term::Positive(versions),
691                                UnavailableReason::Version(reason),
692                            ));
693                    }
694                    ForkedDependencies::Unforked(dependencies) => {
695                        state
696                            .added_dependencies
697                            .entry(next_id)
698                            .or_default()
699                            .insert(version.clone());
700
701                        // Enrich the state with any URLs, etc.
702                        state
703                            .visit_package_version_dependencies(
704                                next_id,
705                                &version,
706                                &self.urls,
707                                &self.indexes,
708                                &dependencies,
709                                &self.git,
710                                &self.workspace_members,
711                                self.selector.resolution_strategy(),
712                            )
713                            .map_err(|err| {
714                                enrich_dependency_error(err, next_id, &version, &state.pubgrub)
715                            })?;
716
717                        // Emit a request to fetch the metadata for each registry package.
718                        self.visit_dependencies(&dependencies, &state, requests)
719                            .map_err(|err| {
720                                enrich_dependency_error(err, next_id, &version, &state.pubgrub)
721                            })?;
722
723                        // Add the dependencies to the state.
724                        state.add_package_version_dependencies(
725                            next_id,
726                            &version,
727                            dependencies,
728                            &self.index,
729                            &self.installed_packages,
730                        );
731                    }
732                    ForkedDependencies::Forked {
733                        mut forks,
734                        diverging_packages,
735                    } => {
736                        state
737                            .added_dependencies
738                            .entry(next_id)
739                            .or_default()
740                            .insert(version.clone());
741
742                        debug!(
743                            "Pre-fork {} took {:.3}s",
744                            state.env,
745                            start.elapsed().as_secs_f32()
746                        );
747
748                        // Prioritize the forks.
749                        match (self.options.fork_strategy, self.options.resolution_mode) {
750                            (ForkStrategy::Fewest, _) | (_, ResolutionMode::Lowest) => {
751                                // Prefer solving forks with lower Python bounds, since they're more
752                                // likely to produce solutions that work for forks with higher
753                                // Python bounds (whereas the inverse is not true).
754                                forks.sort_by(|a, b| {
755                                    a.cmp_requires_python(b)
756                                        .reverse()
757                                        .then_with(|| a.cmp_upper_bounds(b))
758                                });
759                            }
760                            (ForkStrategy::RequiresPython, _) => {
761                                // Otherwise, prefer solving forks with higher Python bounds, since
762                                // we want to prioritize choosing the latest-compatible package
763                                // version for each Python version.
764                                forks.sort_by(|a, b| {
765                                    a.cmp_requires_python(b).then_with(|| a.cmp_upper_bounds(b))
766                                });
767                            }
768                        }
769
770                        for new_fork_state in self.forks_to_fork_states(
771                            state,
772                            &version,
773                            forks,
774                            requests,
775                            &diverging_packages,
776                        ) {
777                            forked_states.push(new_fork_state?);
778                        }
779                        continue 'FORK;
780                    }
781                    ForkedDependencies::RequiresPython(requires_python) => {
782                        if matches!(self.options.fork_strategy, ForkStrategy::RequiresPython)
783                            && state.env.marker_environment().is_none()
784                        {
785                            let forks = fork_version_by_python_requirement(
786                                &requires_python,
787                                &state.python_requirement,
788                                &state.env,
789                            );
790                            if !forks.is_empty() {
791                                debug!(
792                                    "Forking Python requirement `{}` on `{}` for {}=={} ({})",
793                                    state.python_requirement.target(),
794                                    &requires_python,
795                                    next_package,
796                                    version,
797                                    forks
798                                        .iter()
799                                        .map(ToString::to_string)
800                                        .collect::<Vec<_>>()
801                                        .join(", ")
802                                );
803
804                                // Revisit the version in each fork so its dependencies are added
805                                // under the narrowed Python requirement.
806                                let forks = forks
807                                    .into_iter()
808                                    .map(|env| VersionFork {
809                                        env,
810                                        id: next_id,
811                                        version: None,
812                                    })
813                                    .collect();
814                                forked_states
815                                    .extend(self.version_forks_to_fork_states(state, forks));
816                                continue 'FORK;
817                            }
818                        }
819
820                        let versions = state.widen_version_to_gap(
821                            &version,
822                            &self.index,
823                            &self.installed_packages,
824                        );
825                        state
826                            .pubgrub
827                            .add_incompatibility(Incompatibility::custom_term(
828                                next_id,
829                                Term::Positive(versions),
830                                UnavailableReason::Version(UnavailableVersion::RequiresPython(
831                                    requires_python,
832                                )),
833                            ));
834                    }
835                }
836            }
837        }
838        if resolutions.len() > 1 {
839            info!(
840                "Solved your requirements for {} environments",
841                resolutions.len()
842            );
843        }
844        if tracing::enabled!(Level::DEBUG) {
845            for resolution in &resolutions {
846                if let Some(env) = resolution.env.end_user_fork_display() {
847                    let packages: FxHashSet<_> = resolution
848                        .nodes
849                        .iter()
850                        .map(|(package, _)| &package.name)
851                        .collect();
852                    debug!(
853                        "Distinct solution for {env} with {} package(s)",
854                        packages.len()
855                    );
856                }
857            }
858        }
859        for resolution in &resolutions {
860            resolution.trace_resolution();
861        }
862        let resolutions = resolutions
863            .into_iter()
864            .map(|resolution| resolution.finalize(&self.index, &self.git))
865            .collect::<Result<Vec<_>, _>>()?;
866        crate::resolution::from_state(
867            resolutions,
868            self.project.as_ref(),
869            &self.workspace_members,
870            self.requirements.clone(),
871            self.constraints.clone(),
872            self.modifiers.clone(),
873            &self.preferences,
874            &self.hasher,
875            &self.index,
876            self.python_requirement.target().clone(),
877            &self.conflicts,
878            self.selector.resolution_strategy(),
879            self.options.clone(),
880        )
881    }
882
883    /// Convert the dependency [`Fork`]s into [`ForkState`]s.
884    fn forks_to_fork_states<'a, 'index: 'a>(
885        &'a self,
886        current_state: ForkState<'index>,
887        version: &'a Version,
888        forks: Vec<Fork>,
889        requests: &'a MetadataRequests,
890        diverging_packages: &'a BTreeSet<PackageName>,
891    ) -> impl Iterator<Item = Result<ForkState<'index>, ResolveError>> + 'a {
892        debug!(
893            "Splitting resolution on {}=={} over {} into {} resolution{} with separate markers",
894            current_state.pubgrub.package_store[current_state.next],
895            version,
896            diverging_packages
897                .iter()
898                .map(ToString::to_string)
899                .join(", "),
900            forks.len(),
901            if forks.len() == 1 { "" } else { "s" }
902        );
903        assert!(forks.len() >= 2);
904        // This is a somewhat tortured technique to ensure
905        // that our resolver state is only cloned as much
906        // as it needs to be. We basically move the state
907        // into `forked_states`, and then only clone it if
908        // there is at least one more fork to visit.
909        let package = current_state.next;
910        let mut cur_state = Some(current_state);
911        let forks_len = forks.len();
912        forks
913            .into_iter()
914            .enumerate()
915            .map(move |(i, fork)| {
916                let is_last = i == forks_len - 1;
917                let forked_state = cur_state.take().unwrap();
918                if !is_last {
919                    cur_state = Some(forked_state.clone());
920                }
921
922                let env = fork.env.clone();
923                (fork, forked_state.with_env(env))
924            })
925            .map(move |(fork, mut forked_state)| {
926                // Enrich the state with any URLs, etc.
927                forked_state
928                    .visit_package_version_dependencies(
929                        package,
930                        version,
931                        &self.urls,
932                        &self.indexes,
933                        &fork.dependencies,
934                        &self.git,
935                        &self.workspace_members,
936                        self.selector.resolution_strategy(),
937                    )
938                    .map_err(|err| {
939                        enrich_dependency_error(err, package, version, &forked_state.pubgrub)
940                    })?;
941
942                // Emit a request to fetch the metadata for each registry package.
943                self.visit_dependencies(&fork.dependencies, &forked_state, requests)
944                    .map_err(|err| {
945                        enrich_dependency_error(err, package, version, &forked_state.pubgrub)
946                    })?;
947
948                // Add the dependencies to the state.
949                forked_state.add_package_version_dependencies(
950                    package,
951                    version,
952                    fork.dependencies,
953                    &self.index,
954                    &self.installed_packages,
955                );
956
957                Ok(forked_state)
958            })
959    }
960
961    /// Convert the dependency [`Fork`]s into [`ForkState`]s.
962    #[expect(clippy::unused_self)]
963    fn version_forks_to_fork_states<'index>(
964        &self,
965        current_state: ForkState<'index>,
966        forks: Vec<VersionFork>,
967    ) -> impl Iterator<Item = ForkState<'index>> {
968        // This is a somewhat tortured technique to ensure
969        // that our resolver state is only cloned as much
970        // as it needs to be. We basically move the state
971        // into `forked_states`, and then only clone it if
972        // there is at least one more fork to visit.
973        let mut cur_state = Some(current_state);
974        let forks_len = forks.len();
975        forks.into_iter().enumerate().map(move |(i, fork)| {
976            let is_last = i == forks_len - 1;
977            let forked_state = cur_state.take().unwrap();
978            if !is_last {
979                cur_state = Some(forked_state.clone());
980            }
981            let continuation = match fork.version {
982                Some(version) => ForkContinuation::UseVersion {
983                    package: fork.id,
984                    version,
985                },
986                None => ForkContinuation::SelectVersion { package: fork.id },
987            };
988            forked_state
989                .with_env(fork.env)
990                .with_continuation(continuation)
991        })
992    }
993
994    /// Visit a set of [`PubGrubDependency`] entities prior to selection.
995    fn visit_dependencies(
996        &self,
997        dependencies: &[PubGrubDependency],
998        state: &ForkState,
999        requests: &MetadataRequests,
1000    ) -> Result<(), ResolveError> {
1001        for dependency in dependencies {
1002            let PubGrubDependency {
1003                package,
1004                version: _,
1005                parent: _,
1006                source: _,
1007            } = dependency;
1008            let Some(source) = PackageSource::for_prefetch(
1009                package,
1010                &state.fork_urls,
1011                &state.fork_indexes,
1012                &self.urls,
1013            ) else {
1014                continue;
1015            };
1016            self.request_package(package, source, requests)?;
1017        }
1018        Ok(())
1019    }
1020
1021    fn request_package(
1022        &self,
1023        package: &PubGrubPackage,
1024        source: PackageSource<'_>,
1025        requests: &MetadataRequests,
1026    ) -> Result<(), ResolveError> {
1027        // Only request real packages.
1028        let Some(name) = package.name_no_root() else {
1029            return Ok(());
1030        };
1031
1032        match source {
1033            PackageSource::Url(url) => {
1034                // Verify that the package is allowed under the hash-checking policy.
1035                if !self.hasher.allows_url(&url.verbatim) {
1036                    return Err(ResolveError::UnhashedPackage(name.clone()));
1037                }
1038
1039                // Emit a request to fetch the metadata for this distribution.
1040                let dist = Dist::from_url(name.clone(), url.clone())?;
1041                requests.enqueue_metadata(MetadataRequest::Dist(dist))?;
1042            }
1043            PackageSource::Registry(index) => {
1044                requests.enqueue_package(name, index)?;
1045            }
1046        }
1047        Ok(())
1048    }
1049
1050    /// Visit the set of [`PubGrubPackage`] candidates prior to selection. This allows us to fetch
1051    /// metadata for all packages in parallel.
1052    fn pre_visit<'data>(
1053        packages: impl Iterator<
1054            Item = (
1055                Id<PubGrubPackage>,
1056                &'data PubGrubPackage,
1057                &'data Range<Version>,
1058            ),
1059        >,
1060        pre_visited: &mut FxHashMap<Id<PubGrubPackage>, Range<Version>>,
1061        urls: &Urls,
1062        indexes: &Indexes,
1063        python_requirement: &PythonRequirement,
1064        requests: &MetadataRequests,
1065    ) -> Result<(), ResolveError> {
1066        // Iterate over the potential packages, and fetch file metadata for any of them. These
1067        // represent our current best guesses for the versions that we _might_ select.
1068        for (id, package, range) in packages {
1069            let PubGrubPackageInner::Package {
1070                name,
1071                kind: PackageNodeKind::Base,
1072                marker: MarkerTree::TRUE,
1073            } = &**package
1074            else {
1075                continue;
1076            };
1077            // Avoid pre-visiting packages that have any URLs in any fork. At this point we can't
1078            // tell whether they are registry distributions or which url they use.
1079            if urls.any_url(name) {
1080                continue;
1081            }
1082            // Avoid visiting packages that may use an explicit index.
1083            if indexes.contains_key(name) {
1084                continue;
1085            }
1086            // Unit propagation often leaves a package's range unchanged. Although prefetching the
1087            // same package and range is idempotent, selecting its candidate is not free.
1088            if pre_visited.get(&id) == Some(range) {
1089                continue;
1090            }
1091            pre_visited.insert(id, range.clone());
1092            requests.prefetch(name, range, python_requirement)?;
1093        }
1094        Ok(())
1095    }
1096
1097    /// Given a candidate package, choose the next version in range to try.
1098    ///
1099    /// Returns `None` when there are no versions in the given range, rejecting the current partial
1100    /// solution.
1101    // TODO(konsti): re-enable tracing. This trace is crucial to understanding the
1102    // tracing-durations-export diagrams, but it took ~5% resolver thread runtime for apache-airflow
1103    // when I last measured.
1104    #[cfg_attr(feature = "tracing-durations-export", instrument(skip_all, fields(%package)))]
1105    fn choose_version<'index>(
1106        &self,
1107        package: &PubGrubPackage,
1108        id: Id<PubGrubPackage>,
1109        source: PackageSource<'_>,
1110        range: &Range<Version>,
1111        pins: &mut FilePins<'index>,
1112        preferences: &Preferences,
1113        env: &ResolverEnvironment,
1114        python_requirement: &PythonRequirement,
1115        pubgrub: &State<UvDependencyProvider>,
1116        visited: &mut FxHashSet<PackageName>,
1117        requests: &'index MetadataRequests,
1118    ) -> Result<Option<ResolverVersion>, ResolveError> {
1119        match &**package {
1120            PubGrubPackageInner::Root(_) => {
1121                Ok(Some(ResolverVersion::Unforked(MIN_VERSION.clone())))
1122            }
1123
1124            PubGrubPackageInner::Python(_) => {
1125                // Dependencies on Python are only added when a package is incompatible; as such,
1126                // we don't need to do anything here.
1127                Ok(None)
1128            }
1129
1130            PubGrubPackageInner::System(_) => {
1131                // We don't care what the actual version is here, just that it's consistent across
1132                // the dependency graph.
1133                let Some(version) = range.as_singleton() else {
1134                    return Ok(None);
1135                };
1136                Ok(Some(ResolverVersion::Unforked(version.clone())))
1137            }
1138
1139            PubGrubPackageInner::Marker { name, .. }
1140            | PubGrubPackageInner::Extra { name, .. }
1141            | PubGrubPackageInner::Group { name, .. }
1142            | PubGrubPackageInner::Package { name, .. } => match source {
1143                PackageSource::Url(url) => self.choose_version_url(
1144                    id,
1145                    name,
1146                    range,
1147                    url,
1148                    env,
1149                    python_requirement,
1150                    pubgrub,
1151                    pins,
1152                    requests,
1153                ),
1154                PackageSource::Registry(index) => self.choose_version_registry(
1155                    package,
1156                    id,
1157                    name,
1158                    index,
1159                    range,
1160                    preferences,
1161                    env,
1162                    python_requirement,
1163                    pubgrub,
1164                    pins,
1165                    visited,
1166                    requests,
1167                ),
1168            },
1169        }
1170    }
1171
1172    /// Select a version for a URL requirement. Since there is only one version per URL, we return
1173    /// that version if it is in range and `None` otherwise.
1174    fn choose_version_url<'index>(
1175        &self,
1176        id: Id<PubGrubPackage>,
1177        name: &PackageName,
1178        range: &Range<Version>,
1179        url: &VerbatimParsedUrl,
1180        env: &ResolverEnvironment,
1181        python_requirement: &PythonRequirement,
1182        pubgrub: &State<UvDependencyProvider>,
1183        pins: &mut FilePins<'index>,
1184        requests: &'index MetadataRequests,
1185    ) -> Result<Option<ResolverVersion>, ResolveError> {
1186        debug!(
1187            "Searching for a compatible version of {name} @ `{}` ({range})",
1188            url.verbatim
1189        );
1190
1191        let dist = Dist::from_url(name.clone(), url.clone())?;
1192        let registered = requests.metadata(&dist)?;
1193        let response = registered.wait();
1194
1195        // If we failed to fetch the metadata for a URL, we can't proceed.
1196        let metadata = match &*response {
1197            MetadataResponse::Found(archive) => &archive.metadata,
1198            MetadataResponse::Unavailable(reason) => {
1199                self.unavailable_packages
1200                    .pin()
1201                    .insert(name.clone(), reason.into());
1202                return Ok(None);
1203            }
1204            // TODO(charlie): Add derivation chain for URL dependencies. In practice, this isn't
1205            // critical since we fetch URL dependencies _prior_ to invoking the resolver.
1206            MetadataResponse::Error(dist, err) => {
1207                return Err(ResolveError::Dist(
1208                    DistErrorKind::from_requested_dist(dist, &**err),
1209                    dist.clone(),
1210                    DerivationChain::default(),
1211                    err.clone(),
1212                ));
1213            }
1214        };
1215
1216        let version = &metadata.version;
1217
1218        // The version is incompatible with the requirement.
1219        if !range.contains(version) {
1220            return Ok(None);
1221        }
1222
1223        // If the URL points to a pre-built wheel, and the wheel's supported Python versions don't
1224        // match our `Requires-Python`, mark it as incompatible.
1225        if let Dist::Built(dist) = &dist {
1226            let filename = match &dist {
1227                BuiltDist::Registry(dist) => &dist.best_wheel().filename,
1228                BuiltDist::DirectUrl(dist) => &dist.filename,
1229                BuiltDist::GitPath(dist) => &dist.filename,
1230                BuiltDist::Path(dist) => &dist.filename,
1231            };
1232
1233            // If the wheel does not cover a required environment, it is incompatible.
1234            if env.marker_environment().is_none() && !self.options.artifact_environments.is_empty()
1235            {
1236                let wheel_marker = implied_markers(filename, self.options.minimum_libc_version);
1237                // If the caller marked an environment as requiring artifact coverage, ensure it
1238                // has coverage.
1239                for environment_marker in self.options.artifact_environments.iter().copied() {
1240                    // If the platform is part of the current environment...
1241                    if env.included_by_marker(environment_marker)
1242                        && env.included_by_marker(
1243                            find_environments(id, pubgrub).and(environment_marker),
1244                        )
1245                    {
1246                        // ...but the wheel doesn't support it in this fork, it's incompatible.
1247                        if !env.included_by_marker(wheel_marker.and(environment_marker)) {
1248                            return Ok(Some(ResolverVersion::Unavailable(
1249                                version.clone(),
1250                                UnavailableVersion::IncompatibleDist(IncompatibleDist::Wheel(
1251                                    IncompatibleWheel::MissingPlatform(environment_marker),
1252                                )),
1253                            )));
1254                        }
1255                    }
1256                }
1257            }
1258
1259            // If the wheel's Python tag doesn't match the target Python, it's incompatible.
1260            if !python_requirement.target().matches_wheel_tag(filename) {
1261                return Ok(Some(ResolverVersion::Unavailable(
1262                    filename.version.clone(),
1263                    UnavailableVersion::IncompatibleDist(IncompatibleDist::Wheel(
1264                        IncompatibleWheel::Tag(IncompatibleTag::AbiPythonVersion),
1265                    )),
1266                )));
1267            }
1268        }
1269
1270        // The version is incompatible due to its `Requires-Python` requirement.
1271        if let Some(requires_python) = metadata.requires_python.as_ref() {
1272            if !python_requirement.target().is_contained_by(requires_python) {
1273                let kind = if python_requirement.installed() == python_requirement.target() {
1274                    PythonRequirementKind::Installed
1275                } else {
1276                    PythonRequirementKind::Target
1277                };
1278                return Ok(Some(ResolverVersion::Unavailable(
1279                    version.clone(),
1280                    UnavailableVersion::IncompatibleDist(IncompatibleDist::Source(
1281                        IncompatibleSource::RequiresPython(requires_python.clone(), kind),
1282                    )),
1283                )));
1284            }
1285        }
1286
1287        pins.insert_url(name, version, registered);
1288        Ok(Some(ResolverVersion::Unforked(version.clone())))
1289    }
1290
1291    /// Given a candidate registry requirement, choose the next version in range to try, or `None`
1292    /// if there is no version in this range.
1293    fn choose_version_registry<'index>(
1294        &self,
1295        package: &PubGrubPackage,
1296        id: Id<PubGrubPackage>,
1297        name: &PackageName,
1298        index: Option<&IndexMetadata>,
1299        range: &Range<Version>,
1300        preferences: &Preferences,
1301        env: &ResolverEnvironment,
1302        python_requirement: &PythonRequirement,
1303        pubgrub: &State<UvDependencyProvider>,
1304        pins: &mut FilePins<'index>,
1305        visited: &mut FxHashSet<PackageName>,
1306        requests: &'index MetadataRequests,
1307    ) -> Result<Option<ResolverVersion>, ResolveError> {
1308        // Wait for the metadata to be available.
1309        let versions_response = requests.request_package(name, index)?.wait();
1310        let index = index.map(IndexMetadata::url);
1311        visited.insert(name.clone());
1312
1313        let version_maps = match *versions_response {
1314            VersionsResponse::Found(ref version_maps) => version_maps.as_slice(),
1315            VersionsResponse::NoIndex => {
1316                self.unavailable_packages
1317                    .pin()
1318                    .insert(name.clone(), UnavailablePackage::NoIndex);
1319                &[]
1320            }
1321            VersionsResponse::Offline => {
1322                self.unavailable_packages
1323                    .pin()
1324                    .insert(name.clone(), UnavailablePackage::Offline);
1325                &[]
1326            }
1327            VersionsResponse::NotFound => {
1328                self.unavailable_packages
1329                    .pin()
1330                    .insert(name.clone(), UnavailablePackage::NotFound);
1331                &[]
1332            }
1333        };
1334
1335        debug!("Searching for a compatible version of {package} ({range})");
1336
1337        // Find a version.
1338        let Some(candidate) = self.selector.select(
1339            name,
1340            range,
1341            version_maps,
1342            preferences,
1343            &self.installed_packages,
1344            &self.exclusions,
1345            index,
1346            env,
1347            self.tags.as_ref(),
1348        ) else {
1349            // Short circuit: we couldn't find _any_ versions for a package.
1350            return Ok(None);
1351        };
1352
1353        let dist = match candidate.dist() {
1354            CandidateDist::Compatible(dist) => dist,
1355            CandidateDist::Incompatible {
1356                incompatible_dist: incompatibility,
1357                prioritized_dist: _,
1358            } => {
1359                // If the version is incompatible because no distributions are compatible, exit early.
1360                return Ok(Some(ResolverVersion::Unavailable(
1361                    candidate.version().clone(),
1362                    // TODO(charlie): We can avoid this clone; the candidate is dropped here and
1363                    // owns the incompatibility.
1364                    UnavailableVersion::IncompatibleDist(incompatibility.clone()),
1365                )));
1366            }
1367        };
1368
1369        // Check whether the version is incompatible due to its Python requirement.
1370        if let Some((requires_python, incompatibility)) =
1371            Self::check_requires_python(dist, python_requirement)
1372        {
1373            if matches!(self.options.fork_strategy, ForkStrategy::RequiresPython) {
1374                if env.marker_environment().is_none() {
1375                    let forks = fork_version_by_python_requirement(
1376                        requires_python,
1377                        python_requirement,
1378                        env,
1379                    );
1380                    if !forks.is_empty() {
1381                        debug!(
1382                            "Forking Python requirement `{}` on `{}` for {}=={} ({})",
1383                            python_requirement.target(),
1384                            requires_python,
1385                            name,
1386                            candidate.version(),
1387                            forks
1388                                .iter()
1389                                .map(ToString::to_string)
1390                                .collect::<Vec<_>>()
1391                                .join(", ")
1392                        );
1393                        let forks = forks
1394                            .into_iter()
1395                            .map(|env| VersionFork {
1396                                env,
1397                                id,
1398                                version: None,
1399                            })
1400                            .collect();
1401                        return Ok(Some(ResolverVersion::Forked(forks)));
1402                    }
1403                }
1404            }
1405
1406            return Ok(Some(ResolverVersion::Unavailable(
1407                candidate.version().clone(),
1408                UnavailableVersion::IncompatibleDist(incompatibility),
1409            )));
1410        }
1411
1412        // A fork can introduce a stricter Python upper bound, excluding every compatible wheel.
1413        // Only recheck that upper bound: missing wheels for newer Python versions do not establish
1414        // an upper bound on the release's Python support.
1415        if env.marker_environment().is_none()
1416            && let Some(upper_bound) = python_requirement.target().range().upper().specifier()
1417            && !dist
1418                .matches_python_requirement(&RequiresPython::from_specifiers(upper_bound.into()))
1419        {
1420            return Ok(Some(ResolverVersion::Unavailable(
1421                candidate.version().clone(),
1422                UnavailableVersion::IncompatibleDist(IncompatibleDist::Wheel(
1423                    IncompatibleWheel::Tag(IncompatibleTag::AbiPythonVersion),
1424                )),
1425            )));
1426        }
1427
1428        // Check whether this version covers all supported platforms; and, if not, generate a fork.
1429        if let Some(forked) = self.fork_version_registry(
1430            &candidate,
1431            dist,
1432            version_maps,
1433            package,
1434            id,
1435            name,
1436            index,
1437            range,
1438            preferences,
1439            env,
1440            pubgrub,
1441            pins,
1442            requests,
1443        )? {
1444            return Ok(Some(forked));
1445        }
1446
1447        let filename = match dist.for_installation() {
1448            ResolvedDistRef::InstallableRegistrySourceDist { sdist, .. } => sdist
1449                .filename()
1450                .unwrap_or(Cow::Borrowed("unknown filename")),
1451            ResolvedDistRef::InstallableRegistryBuiltDist { wheel, .. } => wheel
1452                .filename()
1453                .unwrap_or(Cow::Borrowed("unknown filename")),
1454            ResolvedDistRef::Installed { .. } => Cow::Borrowed("installed"),
1455        };
1456
1457        debug!(
1458            "Selecting: {}=={} [{}] ({})",
1459            name,
1460            candidate.version(),
1461            candidate.choice_kind(),
1462            filename,
1463        );
1464        self.visit_candidate(&candidate, dist, package, name, pins, requests)?;
1465
1466        let version = candidate.version().clone();
1467        Ok(Some(ResolverVersion::Unforked(version)))
1468    }
1469
1470    /// Determine whether a candidate covers all supported platforms; and, if not, generate a fork.
1471    ///
1472    /// This only ever applies to versions that lack source distributions And, for now, we only
1473    /// apply it in two cases:
1474    ///
1475    /// 1. Local versions, where the non-local version has greater platform coverage. The intent is
1476    ///    such that, if we're resolving PyTorch, and we choose `torch==2.5.2+cpu`, we want to
1477    ///    fork so that we can select `torch==2.5.2` on macOS (since the `+cpu` variant doesn't
1478    ///    include any macOS wheels).
1479    /// 2. Platforms that the user explicitly marks as "required" (opt-in). For example, the user
1480    ///    might require that the generated resolution always includes wheels for x86 macOS, and
1481    ///    fails entirely if the platform is unsupported.
1482    fn fork_version_registry<'index>(
1483        &self,
1484        candidate: &Candidate,
1485        dist: &CompatibleDist,
1486        version_maps: &[VersionMap],
1487        package: &PubGrubPackage,
1488        id: Id<PubGrubPackage>,
1489        name: &PackageName,
1490        index: Option<&IndexUrl>,
1491        range: &Range<Version>,
1492        preferences: &Preferences,
1493        env: &ResolverEnvironment,
1494        pubgrub: &State<UvDependencyProvider>,
1495        pins: &mut FilePins<'index>,
1496        requests: &'index MetadataRequests,
1497    ) -> Result<Option<ResolverVersion>, ResolveError> {
1498        // This only applies to universal resolutions.
1499        let Some(fork_markers) = env.fork_markers() else {
1500            return Ok(None);
1501        };
1502
1503        let artifact_markers = dist.implied_markers();
1504        if artifact_markers.is_true() {
1505            return Ok(None);
1506        }
1507
1508        // If the caller marked an environment as requiring artifact coverage, ensure it has
1509        // coverage.
1510        for marker in self.options.artifact_environments.iter().copied() {
1511            // If the platform is part of the current environment...
1512            if env.included_by_marker(marker) {
1513                // But isn't supported by the distribution in this fork...
1514                if !env.included_by_marker(artifact_markers.and(marker))
1515                    && env.included_by_marker(find_environments(id, pubgrub).and(marker))
1516                {
1517                    // Separate the required environment from the candidate's wheel coverage in
1518                    // this fork, allowing environments in neither set to fall on either side.
1519                    // For example, Darwin == 24 becomes Darwin < 25 when the wheels require
1520                    // Darwin >= 25. This avoids introducing forks for unrelated wheel tags.
1521                    let coverage = dist.implied_markers().and(fork_markers);
1522                    let split = marker.restrict(marker.or(coverage));
1523                    let Some((left, right)) = fork_version_by_marker(env, split) else {
1524                        return Ok(Some(ResolverVersion::Unavailable(
1525                            candidate.version().clone(),
1526                            UnavailableVersion::IncompatibleDist(IncompatibleDist::Wheel(
1527                                IncompatibleWheel::MissingPlatform(marker),
1528                            )),
1529                        )));
1530                    };
1531
1532                    debug!(
1533                        "Forking on required platform `{}` for {}=={} ({})",
1534                        marker.try_to_string().unwrap_or_else(|| "true".to_string()),
1535                        name,
1536                        candidate.version(),
1537                        [&left, &right]
1538                            .iter()
1539                            .map(ToString::to_string)
1540                            .collect::<Vec<_>>()
1541                            .join(", ")
1542                    );
1543                    let forks = vec![
1544                        VersionFork {
1545                            env: left,
1546                            id,
1547                            version: None,
1548                        },
1549                        VersionFork {
1550                            env: right,
1551                            id,
1552                            version: None,
1553                        },
1554                    ];
1555                    return Ok(Some(ResolverVersion::Forked(forks)));
1556                }
1557            }
1558        }
1559
1560        // For now, we only apply this to local versions.
1561        if !candidate.version().is_local() {
1562            return Ok(None);
1563        }
1564
1565        debug!(
1566            "Looking at local version: {}=={}",
1567            name,
1568            candidate.version()
1569        );
1570
1571        // If there's a non-local version...
1572        let range = range.clone().intersection(&Range::singleton(
1573            candidate.version().clone().without_local(),
1574        ));
1575
1576        let Some(base_candidate) = self.selector.select(
1577            name,
1578            &range,
1579            version_maps,
1580            preferences,
1581            &self.installed_packages,
1582            &self.exclusions,
1583            index,
1584            env,
1585            self.tags.as_ref(),
1586        ) else {
1587            return Ok(None);
1588        };
1589        let CandidateDist::Compatible(base_dist) = base_candidate.dist() else {
1590            return Ok(None);
1591        };
1592
1593        // ...and the non-local version has greater platform support...
1594        let mut remainder = {
1595            let mut remainder = base_dist.implied_markers();
1596            remainder = remainder.and(artifact_markers.negate());
1597            remainder
1598        };
1599        if remainder.is_false() {
1600            return Ok(None);
1601        }
1602
1603        // If the remainder isn't relevant to the current environment, there's no need to fork.
1604        // For example, if we're solving for `sys_platform == 'darwin'` but the remainder is
1605        // `sys_platform == 'linux'`, we don't need to fork.
1606        if !env.included_by_marker(remainder) {
1607            return Ok(None);
1608        }
1609
1610        // Similarly, if the local distribution is incompatible with the current environment, then
1611        // use the base distribution instead (but don't fork).
1612        if !env.included_by_marker(artifact_markers) {
1613            let filename = match dist.for_installation() {
1614                ResolvedDistRef::InstallableRegistrySourceDist { sdist, .. } => sdist
1615                    .filename()
1616                    .unwrap_or(Cow::Borrowed("unknown filename")),
1617                ResolvedDistRef::InstallableRegistryBuiltDist { wheel, .. } => wheel
1618                    .filename()
1619                    .unwrap_or(Cow::Borrowed("unknown filename")),
1620                ResolvedDistRef::Installed { .. } => Cow::Borrowed("installed"),
1621            };
1622
1623            debug!(
1624                "Preferring non-local candidate: {}=={} [{}] ({})",
1625                name,
1626                base_candidate.version(),
1627                base_candidate.choice_kind(),
1628                filename,
1629            );
1630            self.visit_candidate(&base_candidate, base_dist, package, name, pins, requests)?;
1631
1632            return Ok(Some(ResolverVersion::Unforked(
1633                base_candidate.version().clone(),
1634            )));
1635        }
1636
1637        // If the implied markers includes _some_ macOS environments, but the remainder doesn't,
1638        // then we can extend the implied markers to include _all_ macOS environments. Same goes for
1639        // Linux and Windows.
1640        //
1641        // The idea here is that the base version could support (e.g.) ARM macOS, but not Intel
1642        // macOS. But if _neither_ version supports Intel macOS, we'd rather use `sys_platform == 'darwin'`
1643        // instead of `sys_platform == 'darwin' and platform_machine == 'arm64'`, since it's much
1644        // simpler, and _neither_ version will succeed with Intel macOS anyway.
1645        for value in [
1646            arcstr::literal!("darwin"),
1647            arcstr::literal!("linux"),
1648            arcstr::literal!("win32"),
1649        ] {
1650            let sys_platform = MarkerTree::expression(MarkerExpression::String {
1651                key: MarkerValueString::SysPlatform,
1652                operator: MarkerOperator::Equal,
1653                value,
1654            });
1655            if artifact_markers.is_disjoint(sys_platform) && !remainder.is_disjoint(sys_platform) {
1656                remainder = remainder.or(sys_platform);
1657            }
1658        }
1659
1660        // Otherwise, we need to fork.
1661        let Some((base_env, local_env)) = fork_version_by_marker(env, remainder) else {
1662            return Ok(None);
1663        };
1664
1665        debug!(
1666            "Forking platform for {}=={} ({})",
1667            name,
1668            candidate.version(),
1669            [&base_env, &local_env]
1670                .iter()
1671                .map(ToString::to_string)
1672                .collect::<Vec<_>>()
1673                .join(", ")
1674        );
1675        self.visit_candidate(candidate, dist, package, name, pins, requests)?;
1676        self.visit_candidate(&base_candidate, base_dist, package, name, pins, requests)?;
1677
1678        let forks = vec![
1679            VersionFork {
1680                env: base_env.clone(),
1681                id,
1682                version: Some(base_candidate.version().clone()),
1683            },
1684            VersionFork {
1685                env: local_env.clone(),
1686                id,
1687                version: Some(candidate.version().clone()),
1688            },
1689        ];
1690        Ok(Some(ResolverVersion::Forked(forks)))
1691    }
1692
1693    /// Visit a selected candidate.
1694    fn visit_candidate<'index>(
1695        &self,
1696        candidate: &Candidate,
1697        dist: &CompatibleDist,
1698        package: &PubGrubPackage,
1699        name: &PackageName,
1700        pins: &mut FilePins<'index>,
1701        requests: &'index MetadataRequests,
1702    ) -> Result<(), ResolveError> {
1703        let index = match dist {
1704            CompatibleDist::InstalledDist(_) => None,
1705            CompatibleDist::SourceDist { sdist, .. }
1706            | CompatibleDist::IncompatibleWheel { sdist, .. } => Some(&sdist.index),
1707            CompatibleDist::CompatibleWheel { wheel, .. } => Some(&wheel.index),
1708        };
1709        let variants_json = if uv_preview::is_enabled(PreviewFeature::WheelVariants)
1710            && let Some(index) = index
1711            && let Some(variants_json) = candidate.prioritized().and_then(|prioritized| {
1712                prioritized
1713                    .variants_json()
1714                    .iter()
1715                    .find(|variants_json| &variants_json.index == index)
1716            }) {
1717            Some(requests.request_variants_json(variants_json)?)
1718        } else {
1719            None
1720        };
1721
1722        let request = if matches!(&**package, PubGrubPackageInner::Package { .. })
1723            && self.dependency_mode.is_transitive()
1724        {
1725            Some(|| {
1726                requests.request_metadata(
1727                    MetadataRequest::Resolved(dist.for_resolution()),
1728                    |request| {
1729                        if name != request.name() {
1730                            return Err(ResolveError::MismatchedPackageName {
1731                                request: "distribution",
1732                                expected: name.clone(),
1733                                actual: request.name().clone(),
1734                            });
1735                        }
1736                        // Verify that the package is allowed under the hash-checking policy.
1737                        if !self
1738                            .hasher
1739                            .allows_package(candidate.name(), candidate.version())
1740                        {
1741                            return Err(ResolveError::UnhashedPackage(candidate.name().clone()));
1742                        }
1743                        Ok(())
1744                    },
1745                )
1746            })
1747        } else {
1748            None
1749        };
1750        pins.insert(candidate, dist, variants_json, request)
1751    }
1752
1753    /// Check if the distribution is incompatible with the Python requirement, and if so, return
1754    /// the incompatibility.
1755    fn check_requires_python<'dist>(
1756        dist: &'dist CompatibleDist,
1757        python_requirement: &PythonRequirement,
1758    ) -> Option<(&'dist VersionSpecifiers, IncompatibleDist)> {
1759        let requires_python = dist.requires_python()?;
1760        if python_requirement.target().is_contained_by(requires_python) {
1761            None
1762        } else {
1763            let incompatibility = if matches!(dist, CompatibleDist::CompatibleWheel { .. }) {
1764                IncompatibleDist::Wheel(IncompatibleWheel::RequiresPython(
1765                    requires_python.clone(),
1766                    if python_requirement.installed() == python_requirement.target() {
1767                        PythonRequirementKind::Installed
1768                    } else {
1769                        PythonRequirementKind::Target
1770                    },
1771                ))
1772            } else {
1773                IncompatibleDist::Source(IncompatibleSource::RequiresPython(
1774                    requires_python.clone(),
1775                    if python_requirement.installed() == python_requirement.target() {
1776                        PythonRequirementKind::Installed
1777                    } else {
1778                        PythonRequirementKind::Target
1779                    },
1780                ))
1781            };
1782            Some((requires_python, incompatibility))
1783        }
1784    }
1785
1786    /// Given a candidate package and version, return its dependencies.
1787    fn get_dependencies_forking(
1788        &self,
1789        id: Id<PubGrubPackage>,
1790        package: &PubGrubPackage,
1791        version: &Version,
1792        pins: &FilePins,
1793        env: &ResolverEnvironment,
1794        python_requirement: &PythonRequirement,
1795        pubgrub: &State<UvDependencyProvider>,
1796    ) -> Result<ForkedDependencies, ResolveError> {
1797        let dependencies =
1798            self.get_dependencies(id, package, version, pins, env, python_requirement, pubgrub)?;
1799        if env.marker_environment().is_some() {
1800            Ok(ForkedDependencies::from_dependencies_platform_specific(
1801                dependencies,
1802            ))
1803        } else {
1804            Ok(ForkedDependencies::from_dependencies_universal(
1805                dependencies,
1806                env,
1807                python_requirement,
1808                &self.conflicts,
1809            ))
1810        }
1811    }
1812
1813    /// Given a candidate package and version, return its dependencies.
1814    #[instrument(skip_all, fields(%package, %version))]
1815    fn get_dependencies(
1816        &self,
1817        id: Id<PubGrubPackage>,
1818        package: &PubGrubPackage,
1819        version: &Version,
1820        pins: &FilePins,
1821        env: &ResolverEnvironment,
1822        python_requirement: &PythonRequirement,
1823        pubgrub: &State<UvDependencyProvider>,
1824    ) -> Result<Dependencies, ResolveError> {
1825        let expander =
1826            RequirementExpander::new(&self.constraints, &self.modifiers, env, python_requirement);
1827        let dependencies = match &**package {
1828            PubGrubPackageInner::Root(_) => {
1829                let requirements = expander.expand(&self.requirements, RequirementContext::Root);
1830
1831                PubGrubDependency::from_requirements(
1832                    &self.conflicts,
1833                    requirements,
1834                    None,
1835                    Some(package),
1836                )
1837            }
1838
1839            PubGrubPackageInner::Package {
1840                name,
1841                kind,
1842                marker: _,
1843            } => {
1844                if uv_preview::is_enabled(PreviewFeature::WheelVariants)
1845                    && let Some(variants_json) = pins.variants_json(name, version)
1846                    && let VariantsJsonResponse::Unavailable(reason) = &*variants_json.wait()
1847                {
1848                    return Ok(Dependencies::Unavailable(
1849                        self.record_unavailable_metadata(name, version, reason),
1850                    ));
1851                }
1852
1853                // If we're excluding transitive dependencies, short-circuit.
1854                if self.dependency_mode.is_direct() {
1855                    return Ok(Dependencies::Unforkable(Vec::default()));
1856                }
1857
1858                let registered = pins
1859                    .metadata(name, version)
1860                    .ok_or_else(|| ResolveError::UnregisteredTask(format!("{name}=={version}")))?;
1861
1862                // If the package does not exist in the registry or locally, we cannot fetch its dependencies
1863                if self.dependency_mode.is_transitive()
1864                    && self.unavailable_packages.pin().contains_key(name)
1865                    && self.installed_packages.get_packages(name).is_empty()
1866                {
1867                    debug_assert!(
1868                        false,
1869                        "Dependencies were requested for a package that is not available"
1870                    );
1871                    return Err(ResolveError::PackageUnavailable(name.clone()));
1872                }
1873
1874                // Wait for the metadata to be available.
1875                let response = registered.wait();
1876
1877                let metadata = match &*response {
1878                    MetadataResponse::Found(archive) => &archive.metadata,
1879                    MetadataResponse::Unavailable(reason) => {
1880                        return Ok(Dependencies::Unavailable(
1881                            self.record_unavailable_metadata(name, version, reason),
1882                        ));
1883                    }
1884                    MetadataResponse::Error(dist, err) => {
1885                        let chain = DerivationChainBuilder::from_state(id, version, pubgrub)
1886                            .unwrap_or_default();
1887                        return Err(ResolveError::Dist(
1888                            DistErrorKind::from_requested_dist(dist, &**err),
1889                            dist.clone(),
1890                            chain,
1891                            err.clone(),
1892                        ));
1893                    }
1894                };
1895
1896                // If there was no requires-python on the index page, we may have an incompatible
1897                // distribution or need to fork.
1898                if let Some(requires_python) = &metadata.requires_python {
1899                    if !python_requirement.target().is_contained_by(requires_python) {
1900                        return Ok(Dependencies::RequiresPython(requires_python.clone()));
1901                    }
1902                }
1903
1904                // Identify any system dependencies based on the index URL.
1905                let system_dependencies = self
1906                    .options
1907                    .torch_backend
1908                    .as_ref()
1909                    .filter(|torch_backend| matches!(torch_backend, TorchStrategy::Cuda { .. }))
1910                    .filter(|torch_backend| torch_backend.has_system_dependency(name))
1911                    .and_then(|_| pins.get(name, version).and_then(ResolvedDist::index))
1912                    .map(IndexUrl::url)
1913                    .and_then(SystemDependency::from_index)
1914                    .into_iter()
1915                    .inspect(|system_dependency| {
1916                        debug!(
1917                            "Adding system dependency `{}` for `{package}@{version}`",
1918                            system_dependency
1919                        );
1920                    })
1921                    .map(PubGrubDependency::from);
1922
1923                let (requirements, context) = match kind {
1924                    PackageNodeKind::Group(group) => (
1925                        metadata
1926                            .dependency_groups
1927                            .get(group)
1928                            .map_or(&[][..], AsRef::as_ref),
1929                        RequirementContext::Group { name, version },
1930                    ),
1931                    PackageNodeKind::Extra(extra) => (
1932                        metadata.requires_dist.as_ref(),
1933                        RequirementContext::Extra {
1934                            name,
1935                            version,
1936                            extra,
1937                        },
1938                    ),
1939                    PackageNodeKind::Base => (
1940                        metadata.requires_dist.as_ref(),
1941                        RequirementContext::Package { name, version },
1942                    ),
1943                };
1944                let requirements = expander.expand(requirements, context);
1945
1946                PubGrubDependency::from_requirements(
1947                    &self.conflicts,
1948                    requirements,
1949                    kind.group(),
1950                    Some(package),
1951                )
1952                .map(|mut dependencies| {
1953                    dependencies.extend(system_dependencies);
1954                    dependencies
1955                })
1956            }
1957
1958            PubGrubPackageInner::Python(_) => return Ok(Dependencies::Unforkable(Vec::default())),
1959
1960            PubGrubPackageInner::System(_) => return Ok(Dependencies::Unforkable(Vec::default())),
1961
1962            // Add a dependency on both the marker and base package.
1963            PubGrubPackageInner::Marker { name, marker } => {
1964                return Ok(Dependencies::Unforkable(
1965                    [MarkerTree::TRUE, *marker]
1966                        .into_iter()
1967                        .map(move |marker| PubGrubDependency {
1968                            package: PubGrubPackage::from(PubGrubPackageInner::Package {
1969                                name: name.clone(),
1970                                kind: PackageNodeKind::Base,
1971                                marker,
1972                            }),
1973                            version: Range::singleton(version.clone()),
1974                            parent: None,
1975                            source: DependencySource::Unspecified,
1976                        })
1977                        .collect(),
1978                ));
1979            }
1980
1981            // Add a dependency on both the extra and base package, with and without the marker.
1982            PubGrubPackageInner::Extra {
1983                name,
1984                extra,
1985                marker,
1986            } => {
1987                return Ok(Dependencies::Unforkable(
1988                    [MarkerTree::TRUE, *marker]
1989                        .into_iter()
1990                        .dedup()
1991                        .flat_map(move |marker| {
1992                            [None, Some(extra)]
1993                                .into_iter()
1994                                .map(move |extra| PubGrubDependency {
1995                                    package: PubGrubPackage::from(PubGrubPackageInner::Package {
1996                                        name: name.clone(),
1997                                        kind: extra
1998                                            .cloned()
1999                                            .map_or(PackageNodeKind::Base, PackageNodeKind::Extra),
2000                                        marker,
2001                                    }),
2002                                    version: Range::singleton(version.clone()),
2003                                    parent: None,
2004                                    source: DependencySource::Unspecified,
2005                                })
2006                        })
2007                        .collect(),
2008                ));
2009            }
2010
2011            // Add a dependency on the dependency group, with and without the marker.
2012            PubGrubPackageInner::Group {
2013                name,
2014                group,
2015                marker,
2016            } => {
2017                return Ok(Dependencies::Unforkable(
2018                    [MarkerTree::TRUE, *marker]
2019                        .into_iter()
2020                        .dedup()
2021                        .map(|marker| PubGrubDependency {
2022                            package: PubGrubPackage::from(PubGrubPackageInner::Package {
2023                                name: name.clone(),
2024                                kind: PackageNodeKind::Group(group.clone()),
2025                                marker,
2026                            }),
2027                            version: Range::singleton(version.clone()),
2028                            parent: None,
2029                            source: DependencySource::Unspecified,
2030                        })
2031                        .collect(),
2032                ));
2033            }
2034        };
2035        Ok(match dependencies {
2036            Ok(dependencies) => Dependencies::Available(dependencies),
2037            Err(requirement) => {
2038                Dependencies::Unavailable(UnavailableVersion::UnsatisfiableDependency(requirement))
2039            }
2040        })
2041    }
2042
2043    fn record_unavailable_metadata(
2044        &self,
2045        name: &PackageName,
2046        version: &Version,
2047        reason: &MetadataUnavailable,
2048    ) -> UnavailableVersion {
2049        let unavailable_version = UnavailableVersion::from(reason);
2050        let message = unavailable_version.singular_message();
2051        if let Some(error) = reason.source() {
2052            warn!("{name} {message}: {error}");
2053        } else {
2054            warn!("{name} {message}");
2055        }
2056        let incomplete_packages = self.incomplete_packages.pin();
2057        let versions = incomplete_packages.get_or_insert(
2058            name.clone(),
2059            HashMap::builder().resize_mode(ResizeMode::Blocking).build(),
2060        );
2061        versions.pin().insert(version.clone(), reason.clone());
2062        unavailable_version
2063    }
2064
2065    /// Fetch the metadata for a stream of packages and versions.
2066    async fn fetch<Provider: ResolverProvider>(
2067        self: Arc<Self>,
2068        provider: Arc<Provider>,
2069        request_stream: Receiver<Request>,
2070    ) -> Result<(), ResolveError> {
2071        let mut response_stream = ReceiverStream::new(request_stream)
2072            .map(|request| self.process_request(request, &*provider).boxed_local())
2073            // Allow as many futures as possible to start in the background.
2074            // Backpressure is provided by at a more granular level by `DistributionDatabase`
2075            // and `SourceDispatch`, as well as the bounded request channel.
2076            .buffer_unordered(usize::MAX);
2077
2078        while let Some(response) = response_stream.next().await {
2079            match response? {
2080                Some(Response::Package(name, index, version_map)) => {
2081                    trace!("Received package metadata for: {name}");
2082                    if let Some(index) = index {
2083                        self.index
2084                            .explicit()
2085                            .done((name, index), Arc::new(version_map));
2086                    } else {
2087                        self.index.implicit().done(name, Arc::new(version_map));
2088                    }
2089                }
2090                Some(Response::Installed { dist, metadata }) => {
2091                    trace!("Received installed distribution metadata for: {dist}");
2092                    self.index
2093                        .distributions()
2094                        .done(dist.distribution_id(), Arc::new(metadata));
2095                }
2096                Some(Response::Dist { dist, metadata }) => {
2097                    let dist_kind = match dist {
2098                        Dist::Built(_) => "built",
2099                        Dist::Source(_) => "source",
2100                    };
2101                    trace!("Received {dist_kind} distribution metadata for: {dist}");
2102                    if let MetadataResponse::Unavailable(reason) = &metadata {
2103                        let message = UnavailableVersion::from(reason).singular_message();
2104                        if let Some(err) = reason.source() {
2105                            // Show the detailed error for metadata parse errors.
2106                            warn!("`{dist}` {message}: {err}");
2107                        } else {
2108                            warn!("`{dist}` {message}");
2109                        }
2110                    }
2111                    self.index
2112                        .distributions()
2113                        .done(dist.distribution_id(), Arc::new(metadata));
2114                }
2115                Some(Response::VariantsJson {
2116                    variants_json,
2117                    response,
2118                }) => {
2119                    trace!("Received variant metadata for: {}", variants_json.filename);
2120                    self.index
2121                        .variants_json()
2122                        .done(variants_json, Arc::new(response));
2123                }
2124                None => {}
2125            }
2126        }
2127
2128        Ok::<(), ResolveError>(())
2129    }
2130
2131    #[instrument(skip_all, fields(%request))]
2132    async fn process_request<Provider: ResolverProvider>(
2133        &self,
2134        request: Request,
2135        provider: &Provider,
2136    ) -> Result<Option<Response>, ResolveError> {
2137        match request {
2138            Request::VariantsJson(variants_json) => {
2139                let response = provider
2140                    .fetch_variants_json(
2141                        &variants_json.filename.name,
2142                        &variants_json.file,
2143                        &variants_json.index,
2144                    )
2145                    .boxed_local()
2146                    .await
2147                    .map_err(ResolveError::Client)?;
2148                Ok(Some(Response::VariantsJson {
2149                    variants_json,
2150                    response,
2151                }))
2152            }
2153            // Fetch package metadata from the registry.
2154            Request::Package(package_name, index) => {
2155                let package_versions = provider
2156                    .get_package_versions(&package_name, index.as_ref())
2157                    .boxed_local()
2158                    .await
2159                    .map_err(ResolveError::Client)?;
2160
2161                Ok(Some(Response::Package(
2162                    package_name,
2163                    index.map(IndexMetadata::into_url),
2164                    package_versions,
2165                )))
2166            }
2167
2168            // Fetch distribution metadata from the distribution database.
2169            Request::Dist(dist) => {
2170                if let Some(version) = dist.version() {
2171                    if let Some(index) = dist.index() {
2172                        // Check the implicit indexes for pre-provided metadata.
2173                        let versions_response = self.index.implicit().get(dist.name());
2174                        if let Some(VersionsResponse::Found(version_maps)) =
2175                            versions_response.as_deref()
2176                        {
2177                            for version_map in version_maps {
2178                                if version_map.index() == Some(index) {
2179                                    let Some(metadata) = version_map.get_metadata(version) else {
2180                                        continue;
2181                                    };
2182                                    debug!("Found registry-provided metadata for: {dist}");
2183                                    return Ok(Some(Response::Dist {
2184                                        dist,
2185                                        metadata: MetadataResponse::Found(
2186                                            ArchiveMetadata::from_metadata23(metadata),
2187                                        ),
2188                                    }));
2189                                }
2190                            }
2191                        }
2192
2193                        // Check the explicit indexes for pre-provided metadata.
2194                        let versions_response = self
2195                            .index
2196                            .explicit()
2197                            .get(&(dist.name().clone(), index.clone()));
2198                        if let Some(VersionsResponse::Found(version_maps)) =
2199                            versions_response.as_deref()
2200                        {
2201                            for version_map in version_maps {
2202                                let Some(metadata) = version_map.get_metadata(version) else {
2203                                    continue;
2204                                };
2205                                debug!("Found registry-provided metadata for: {dist}");
2206                                return Ok(Some(Response::Dist {
2207                                    dist,
2208                                    metadata: MetadataResponse::Found(
2209                                        ArchiveMetadata::from_metadata23(metadata),
2210                                    ),
2211                                }));
2212                            }
2213                        }
2214                    }
2215                }
2216
2217                let metadata = provider
2218                    .get_or_build_wheel_metadata(&dist)
2219                    .boxed_local()
2220                    .await?;
2221
2222                if let MetadataResponse::Found(metadata) = &metadata {
2223                    if &metadata.metadata.name != dist.name() {
2224                        return Err(ResolveError::MismatchedPackageName {
2225                            request: "distribution metadata",
2226                            expected: dist.name().clone(),
2227                            actual: metadata.metadata.name.clone(),
2228                        });
2229                    }
2230                }
2231
2232                Ok(Some(Response::Dist { dist, metadata }))
2233            }
2234
2235            Request::Installed(dist) => {
2236                let metadata = provider.get_installed_metadata(&dist).boxed_local().await?;
2237
2238                if let MetadataResponse::Found(metadata) = &metadata {
2239                    if &metadata.metadata.name != dist.name() {
2240                        return Err(ResolveError::MismatchedPackageName {
2241                            request: "installed metadata",
2242                            expected: dist.name().clone(),
2243                            actual: metadata.metadata.name.clone(),
2244                        });
2245                    }
2246                }
2247
2248                Ok(Some(Response::Installed { dist, metadata }))
2249            }
2250
2251            // Pre-fetch the package and distribution metadata.
2252            Request::Prefetch(package_name, range, python_requirement) => {
2253                // Wait for the package metadata to become available.
2254                let versions_response = self
2255                    .index
2256                    .implicit()
2257                    .get_registered(package_name.clone())
2258                    .ok_or_else(|| ResolveError::UnregisteredTask(package_name.to_string()))?
2259                    .wait()
2260                    .await;
2261
2262                let version_map = match *versions_response {
2263                    VersionsResponse::Found(ref version_map) => version_map,
2264                    // Short-circuit if we did not find any versions for the package
2265                    VersionsResponse::NoIndex => {
2266                        self.unavailable_packages
2267                            .pin()
2268                            .insert(package_name.clone(), UnavailablePackage::NoIndex);
2269
2270                        return Ok(None);
2271                    }
2272                    VersionsResponse::Offline => {
2273                        self.unavailable_packages
2274                            .pin()
2275                            .insert(package_name.clone(), UnavailablePackage::Offline);
2276
2277                        return Ok(None);
2278                    }
2279                    VersionsResponse::NotFound => {
2280                        self.unavailable_packages
2281                            .pin()
2282                            .insert(package_name.clone(), UnavailablePackage::NotFound);
2283
2284                        return Ok(None);
2285                    }
2286                };
2287
2288                // We don't have access to the fork state when prefetching.
2289                let env = ResolverEnvironment::universal(vec![]);
2290
2291                // Try to find a compatible version. If there aren't any compatible versions,
2292                // short-circuit.
2293                let Some(candidate) = self.selector.select(
2294                    &package_name,
2295                    &range,
2296                    version_map,
2297                    &self.preferences,
2298                    &self.installed_packages,
2299                    &self.exclusions,
2300                    None,
2301                    &env,
2302                    self.tags.as_ref(),
2303                ) else {
2304                    return Ok(None);
2305                };
2306
2307                // If there is not a compatible distribution, short-circuit.
2308                let Some(dist) = candidate.compatible() else {
2309                    return Ok(None);
2310                };
2311
2312                // If the registry provided metadata for this distribution, use it.
2313                for version_map in version_map {
2314                    if let Some(metadata) = version_map.get_metadata(candidate.version()) {
2315                        let dist = dist.for_resolution();
2316                        if version_map.index() == dist.index() {
2317                            debug!("Found registry-provided metadata for: {dist}");
2318
2319                            let metadata =
2320                                MetadataResponse::Found(ArchiveMetadata::from_metadata23(metadata));
2321
2322                            let dist = dist.to_owned();
2323                            if &package_name != dist.name() {
2324                                return Err(ResolveError::MismatchedPackageName {
2325                                    request: "distribution",
2326                                    expected: package_name,
2327                                    actual: dist.name().clone(),
2328                                });
2329                            }
2330
2331                            let response = match dist {
2332                                ResolvedDist::Installable { dist, .. } => Response::Dist {
2333                                    dist: (*dist).clone(),
2334                                    metadata,
2335                                },
2336                                ResolvedDist::Installed { dist } => Response::Installed {
2337                                    dist: (*dist).clone(),
2338                                    metadata,
2339                                },
2340                            };
2341
2342                            return Ok(Some(response));
2343                        }
2344                    }
2345                }
2346
2347                // Avoid prefetching source distributions with unbounded lower-bound ranges. This
2348                // often leads to failed attempts to build legacy versions of packages that are
2349                // incompatible with modern build tools.
2350                if dist.wheel().is_none() {
2351                    if !self.selector.use_highest_version(&package_name, &env) {
2352                        if let Some((lower, _)) = range.iter().next() {
2353                            if lower == Bound::Unbounded {
2354                                debug!(
2355                                    "Skipping prefetch for unbounded minimum-version range: {package_name} ({range})"
2356                                );
2357                                return Ok(None);
2358                            }
2359                        }
2360                    }
2361                }
2362
2363                // Validate the Python requirement.
2364                if let Some(requires_python) = dist.requires_python() {
2365                    if !python_requirement.target().is_contained_by(requires_python) {
2366                        return Ok(None);
2367                    }
2368                }
2369
2370                // Verify that the package is allowed under the hash-checking policy.
2371                if !self
2372                    .hasher
2373                    .allows_package(candidate.name(), candidate.version())
2374                {
2375                    return Ok(None);
2376                }
2377
2378                // Emit a request to fetch the metadata for this version.
2379                let dist = dist.for_resolution();
2380                if self.index.distributions().register(dist.distribution_id()) {
2381                    let dist = dist.to_owned();
2382                    if &package_name != dist.name() {
2383                        return Err(ResolveError::MismatchedPackageName {
2384                            request: "distribution",
2385                            expected: package_name,
2386                            actual: dist.name().clone(),
2387                        });
2388                    }
2389
2390                    let response = match dist {
2391                        ResolvedDist::Installable { dist, .. } => {
2392                            let metadata = provider
2393                                .get_or_build_wheel_metadata(&dist)
2394                                .boxed_local()
2395                                .await?;
2396
2397                            Response::Dist {
2398                                dist: (*dist).clone(),
2399                                metadata,
2400                            }
2401                        }
2402                        ResolvedDist::Installed { dist } => {
2403                            let metadata =
2404                                provider.get_installed_metadata(&dist).boxed_local().await?;
2405
2406                            Response::Installed {
2407                                dist: (*dist).clone(),
2408                                metadata,
2409                            }
2410                        }
2411                    };
2412
2413                    Ok(Some(response))
2414                } else {
2415                    Ok(None)
2416                }
2417            }
2418        }
2419    }
2420
2421    fn convert_no_solution_err(
2422        &self,
2423        mut err: pubgrub::NoSolutionError<UvDependencyProvider>,
2424        fork_urls: ForkUrls,
2425        fork_indexes: ForkIndexes,
2426        known_versions: &FxHashMap<PackageName, Arc<[Version]>>,
2427        env: ResolverEnvironment,
2428        current_environment: MarkerEnvironment,
2429        visited: &FxHashSet<PackageName>,
2430    ) -> ResolveError {
2431        err = NoSolutionError::collapse_local_version_segments(NoSolutionError::collapse_proxies(
2432            err,
2433        ));
2434        err = NoSolutionError::narrow_widened_sets(err, known_versions);
2435
2436        let mut unavailable_packages = FxHashMap::default();
2437        for package in derivation_tree_packages(&err) {
2438            if let PubGrubPackageInner::Package { name, .. } = &**package {
2439                if let Some(reason) = self.unavailable_packages.pin().get(name) {
2440                    unavailable_packages.insert(name.clone(), reason.clone());
2441                }
2442            }
2443        }
2444
2445        let mut incomplete_packages = FxHashMap::default();
2446        let incomplete_packages_cache = self.incomplete_packages.pin();
2447        for package in derivation_tree_packages(&err) {
2448            if let PubGrubPackageInner::Package { name, .. } = &**package
2449                && let Some(versions) = incomplete_packages_cache.get(name)
2450            {
2451                for (version, reason) in &versions.pin() {
2452                    incomplete_packages
2453                        .entry(name.clone())
2454                        .or_insert_with(BTreeMap::default)
2455                        .insert(version.clone(), reason.clone());
2456                }
2457            }
2458        }
2459
2460        let mut available_indexes = FxHashMap::default();
2461        let mut included_versions = FxHashMap::default();
2462        let mut available_versions = FxHashMap::default();
2463
2464        let available_version_cutoff: Option<jiff::Timestamp> =
2465            std::env::var(EnvVars::UV_INTERNAL__TEST_AVAILABLE_VERSION_CUTOFF)
2466                .ok()
2467                .and_then(|s| s.parse().ok());
2468
2469        for package in derivation_tree_packages(&err) {
2470            let Some(name) = package.name() else { continue };
2471            if !visited.contains(name) {
2472                // Avoid including version data for packages that exist in the derivation
2473                // tree, but were never visited during resolution. We _may_ have metadata for
2474                // these packages, but it's non-deterministic, and omitting them ensures that
2475                // we represent the state of the resolver at the time of failure.
2476                continue;
2477            }
2478            let versions_response = if let Some(index) = fork_indexes.get(name) {
2479                self.index
2480                    .explicit()
2481                    .get(&(name.clone(), index.url().clone()))
2482            } else {
2483                self.index.implicit().get(name)
2484            };
2485            if let Some(response) = versions_response {
2486                if let VersionsResponse::Found(ref version_maps) = *response {
2487                    // Track included and available versions, across all indexes.
2488                    for version_map in version_maps {
2489                        let package_included_versions = included_versions
2490                            .entry(name.clone())
2491                            .or_insert_with(BTreeSet::new);
2492                        let package_available_versions = available_versions
2493                            .entry(name.clone())
2494                            .or_insert_with(BTreeSet::new);
2495
2496                        for (version, dists) in version_map.iter(&Ranges::full()) {
2497                            // Included versions are those that survive the effective
2498                            // `exclude-newer` filter used during resolution. Files with
2499                            // missing upload times are treated as excluded (matching
2500                            // the resolution behavior in `version_map.rs`).
2501                            let excluded_from_included = || {
2502                                let Some(included_version_cutoff) =
2503                                    version_map.included_version_cutoff()
2504                                else {
2505                                    return false;
2506                                };
2507                                let Some(prioritized_dist) = dists.prioritized_dist() else {
2508                                    return true;
2509                                };
2510                                prioritized_dist.files().all(|file| {
2511                                    file.upload_time_utc_ms.is_none_or(|upload_time| {
2512                                        upload_time >= included_version_cutoff.as_millisecond()
2513                                    })
2514                                })
2515                            };
2516
2517                            if !excluded_from_included() {
2518                                package_included_versions.insert(version.clone());
2519                            }
2520
2521                            // Available versions are used in resolver error reporting,
2522                            // and can be bounded by a test-only cutoff for deterministic
2523                            // snapshots. Files with missing upload times are *not*
2524                            // excluded, since we only filter versions we can confirm
2525                            // were published after the cutoff.
2526                            let excluded_from_available = || {
2527                                let Some(ref exclude_newer) = available_version_cutoff else {
2528                                    return false;
2529                                };
2530                                let Some(prioritized_dist) = dists.prioritized_dist() else {
2531                                    return false;
2532                                };
2533                                prioritized_dist.files().all(|file| {
2534                                    file.upload_time_utc_ms.is_some_and(|upload_time| {
2535                                        upload_time >= exclude_newer.as_millisecond()
2536                                    })
2537                                })
2538                            };
2539
2540                            if !excluded_from_available() {
2541                                package_available_versions.insert(version.clone());
2542                            }
2543                        }
2544                    }
2545
2546                    // Track the indexes in which the package is available.
2547                    available_indexes
2548                        .entry(name.clone())
2549                        .or_insert(BTreeSet::new())
2550                        .extend(
2551                            version_maps
2552                                .iter()
2553                                .filter_map(|version_map| version_map.index().cloned()),
2554                        );
2555                }
2556            }
2557        }
2558
2559        ResolveError::NoSolution(Box::new(NoSolutionError::new(
2560            err,
2561            self.index.clone(),
2562            included_versions,
2563            available_versions,
2564            available_indexes,
2565            self.selector.clone(),
2566            self.python_requirement.clone(),
2567            self.locations.clone(),
2568            self.capabilities.clone(),
2569            unavailable_packages,
2570            incomplete_packages,
2571            fork_urls,
2572            fork_indexes,
2573            env,
2574            current_environment,
2575            self.tags.clone(),
2576            self.workspace_members.clone(),
2577            self.options.clone(),
2578        )))
2579    }
2580
2581    fn on_progress(&self, package: &PubGrubPackage, version: &Version) {
2582        if let Some(reporter) = self.reporter.as_ref() {
2583            match &**package {
2584                PubGrubPackageInner::Root(_) => {}
2585                PubGrubPackageInner::Python(_) => {}
2586                PubGrubPackageInner::System(_) => {}
2587                PubGrubPackageInner::Marker { .. } => {}
2588                PubGrubPackageInner::Extra { .. } => {}
2589                PubGrubPackageInner::Group { .. } => {}
2590                PubGrubPackageInner::Package { name, .. } => {
2591                    reporter.on_progress(name, &VersionOrUrlRef::Version(version));
2592                }
2593            }
2594        }
2595    }
2596
2597    fn on_complete(&self) {
2598        if let Some(reporter) = self.reporter.as_ref() {
2599            reporter.on_complete();
2600        }
2601    }
2602}
2603
2604/// All known versions for each package, from the version maps and the installed packages,
2605/// used to keep the version sets in the partial solution minimal.
2606#[derive(Clone, Default)]
2607struct KnownVersions(FxHashMap<PackageName, Arc<[Version]>>);
2608
2609impl KnownVersions {
2610    /// Returns the sorted, deduplicated candidate universe used to widen version sets.
2611    ///
2612    /// Results are cached on the first call per package.
2613    ///
2614    /// Every selectable version must be present: omitting one could extend an incompatibility
2615    /// across it, while including an unselectable version only prevents a possible simplification.
2616    /// The result is therefore conservative, including yanked and otherwise unavailable versions
2617    /// from every index plus installed versions missing from the indexes. Versions past the
2618    /// exclude-newer cutoff are omitted because resolution treats them as nonexistent.
2619    ///
2620    /// Non-blocking: Returns `None` if the version map hasn't been fetched yet, or if the
2621    /// package is not a registry package.
2622    fn get_or_update<'a, InstalledPackages: InstalledPackagesProvider>(
2623        &'a mut self,
2624        index: &InMemoryIndex,
2625        installed_packages: &InstalledPackages,
2626        source: PackageSource<'_>,
2627        package: &PubGrubPackage,
2628    ) -> Option<&'a [Version]> {
2629        let name = package.name_no_root()?;
2630        // Versions of packages from a URL or the workspace are not registry versions.
2631        let PackageSource::Registry(index_metadata) = source else {
2632            return None;
2633        };
2634        if !self.0.contains_key(name) {
2635            let response = if let Some(index_metadata) = index_metadata {
2636                index
2637                    .explicit()
2638                    .get(&(name.clone(), index_metadata.url().clone()))?
2639            } else {
2640                index.implicit().get(name)?
2641            };
2642            let VersionsResponse::Found(ref version_maps) = *response else {
2643                return None;
2644            };
2645            let mut versions: Vec<Version> = version_maps
2646                .iter()
2647                .flat_map(|version_map| version_map.included_versions().cloned())
2648                .chain(
2649                    installed_packages
2650                        .get_packages(name)
2651                        .iter()
2652                        .map(|dist| dist.version().clone()),
2653                )
2654                .collect();
2655            versions.sort_unstable();
2656            versions.dedup();
2657            self.0.insert(name.clone(), versions.into());
2658        }
2659        Some(&self.0[name][..])
2660    }
2661}
2662
2663/// The operation to resume after creating a fork.
2664#[derive(Clone, Default)]
2665enum ForkContinuation {
2666    /// Run unit propagation and choose the next package.
2667    #[default]
2668    Propagate,
2669    /// Select a version for a package whose constraints have already been propagated.
2670    SelectVersion { package: Id<PubGrubPackage> },
2671    /// Use the version selected for this package by the fork operation.
2672    UseVersion {
2673        package: Id<PubGrubPackage>,
2674        version: Version,
2675    },
2676}
2677
2678/// State that is used during unit propagation in the resolver, one instance per fork.
2679#[derive(Clone)]
2680pub(crate) struct ForkState<'index> {
2681    /// The internal state used by the resolver.
2682    ///
2683    /// Note that not all parts of this state are strictly internal. For
2684    /// example, the edges in the dependency graph generated as part of the
2685    /// output of resolution are derived from the "incompatibilities" tracked
2686    /// in this state. We also ultimately retrieve the final set of version
2687    /// assignments (to packages) from this state's "partial solution."
2688    pubgrub: State<UvDependencyProvider>,
2689    /// The operation to resume when this fork is next visited.
2690    continuation: ForkContinuation,
2691    /// The next package on which to run unit propagation.
2692    next: Id<PubGrubPackage>,
2693    /// The set of pinned versions we accrue throughout resolution.
2694    ///
2695    /// The key of this map is a package name, and each package name maps to
2696    /// a set of versions for that package. Each version in turn is mapped
2697    /// to the concrete distribution selected for installation, along with the
2698    /// concrete distribution whose metadata was used during resolution.
2699    /// After resolution is finished, this map is consulted to recover both the
2700    /// locked artifact and the metadata backing the resolved dependency edges.
2701    pins: FilePins<'index>,
2702    /// Ensure we don't have duplicate URLs in any branch.
2703    ///
2704    /// Unlike [`Urls`], we add only the URLs we have seen in this branch, and there can be only
2705    /// one URL per package. By prioritizing direct URL dependencies over registry dependencies,
2706    /// this map is populated for all direct URL packages before we look at any registry packages.
2707    fork_urls: ForkUrls,
2708    /// Ensure we don't have duplicate indexes in any branch.
2709    ///
2710    /// Unlike [`Indexes`], we add only the indexes we have seen in this branch, and there can be
2711    /// only one index per package.
2712    fork_indexes: ForkIndexes,
2713    /// When dependencies for a package are retrieved, this map of priorities
2714    /// is updated based on how each dependency was specified. Certain types
2715    /// of dependencies have more "priority" than others (like direct URL
2716    /// dependencies). These priorities help determine which package to
2717    /// consider next during resolution.
2718    priorities: PubGrubPriorities,
2719    /// This keeps track of the set of versions for each package that we've
2720    /// already visited during resolution. This avoids doing redundant work.
2721    added_dependencies: FxHashMap<Id<PubGrubPackage>, FxHashSet<Version>>,
2722    /// The last range scheduled for prefetch for each undecided package.
2723    pre_visited: FxHashMap<Id<PubGrubPackage>, Range<Version>>,
2724    /// The last version selected for each package and range in a specific environment.
2725    selected_versions: FxHashMap<Id<PubGrubPackage>, (Range<Version>, Version)>,
2726    /// A cache for parsed version maps.
2727    ///
2728    /// Per fork, since the index for a package can differ between forks.
2729    known_versions: KnownVersions,
2730    /// The marker expression that created this state.
2731    ///
2732    /// The root state always corresponds to a marker expression that is always
2733    /// `true` for every `MarkerEnvironment`.
2734    ///
2735    /// In non-universal mode, forking never occurs and so this marker
2736    /// expression is always `true`.
2737    ///
2738    /// Whenever dependencies are fetched, all requirement specifications
2739    /// are checked for disjointness with the marker expression of the fork
2740    /// in which those dependencies were fetched. If a requirement has a
2741    /// completely disjoint marker expression (i.e., it can never be true given
2742    /// that the marker expression that provoked the fork is true), then that
2743    /// dependency is completely ignored.
2744    env: ResolverEnvironment,
2745    /// The Python requirement for this fork. Defaults to the Python requirement for
2746    /// the resolution, but may be narrowed if a `python_version` marker is present
2747    /// in a given fork.
2748    ///
2749    /// For example, in:
2750    /// ```text
2751    /// numpy >=1.26 ; python_version >= "3.9"
2752    /// numpy <1.26 ; python_version < "3.9"
2753    /// ```
2754    ///
2755    /// The top fork has a narrower Python compatibility range, and thus can find a
2756    /// solution that omits Python 3.8 support.
2757    python_requirement: PythonRequirement,
2758    conflict_tracker: ConflictTracker,
2759    /// Prefetch package versions for packages with many rejected versions.
2760    ///
2761    /// Tracked on the fork state to avoid counting each identical version between forks as new try.
2762    prefetcher: BatchPrefetcher,
2763}
2764
2765impl<'index> ForkState<'index> {
2766    fn new(
2767        pubgrub: State<UvDependencyProvider>,
2768        env: ResolverEnvironment,
2769        python_requirement: PythonRequirement,
2770        prefetcher: BatchPrefetcher,
2771    ) -> Self {
2772        Self {
2773            continuation: ForkContinuation::Propagate,
2774            next: pubgrub.root_package,
2775            pubgrub,
2776            pins: FilePins::default(),
2777            fork_urls: ForkUrls::default(),
2778            fork_indexes: ForkIndexes::default(),
2779            priorities: PubGrubPriorities::default(),
2780            added_dependencies: FxHashMap::default(),
2781            pre_visited: FxHashMap::default(),
2782            selected_versions: FxHashMap::default(),
2783            known_versions: KnownVersions::default(),
2784            env,
2785            python_requirement,
2786            conflict_tracker: ConflictTracker::default(),
2787            prefetcher,
2788        }
2789    }
2790
2791    /// Visit the dependencies for the selected version of the current package, incorporating any
2792    /// relevant URLs and pinned indexes into the [`ForkState`].
2793    fn visit_package_version_dependencies(
2794        &mut self,
2795        for_package: Id<PubGrubPackage>,
2796        for_version: &Version,
2797        urls: &Urls,
2798        indexes: &Indexes,
2799        dependencies: &[PubGrubDependency],
2800        git: &GitResolver,
2801        workspace_members: &BTreeSet<PackageName>,
2802        resolution_strategy: &ResolutionStrategy,
2803    ) -> Result<(), ResolveError> {
2804        for dependency in dependencies {
2805            let PubGrubDependency {
2806                package,
2807                version,
2808                parent: _,
2809                source,
2810            } = dependency;
2811
2812            let mut has_url = false;
2813            if let Some(name) = package.name() {
2814                // From the [`Requirement`] to [`PubGrubDependency`] conversion, we get a URL if the
2815                // requirement was a URL requirement. `Urls` applies canonicalization to this and
2816                // override URLs to both URL and registry requirements, which we then check for
2817                // conflicts using [`ForkUrl`].
2818                for url in urls.get_url(&self.env, name, source.verbatim_url(), git)? {
2819                    self.fork_urls.insert(name, url, &self.env)?;
2820                    has_url = true;
2821                }
2822
2823                if let Some(index) = source.explicit_index() {
2824                    self.fork_indexes.insert(name, index, &self.env)?;
2825                }
2826
2827                // If the package is pinned to an exact index, add it to the fork.
2828                for index in indexes.get(name, &self.env) {
2829                    self.fork_indexes.insert(name, index, &self.env)?;
2830                }
2831            }
2832
2833            if let Some(name) = self.pubgrub.package_store[for_package]
2834                .name_no_root()
2835                .filter(|name| !workspace_members.contains(name))
2836            {
2837                debug!(
2838                    "Adding transitive dependency for {name}=={for_version}: {package}{version}"
2839                );
2840            } else {
2841                // A dependency from the root package or `requirements.txt`.
2842                debug!("Adding direct dependency: {package}{version}");
2843
2844                // Warn the user if a direct dependency lacks a lower bound in `--lowest` resolution.
2845                let missing_lower_bound = version
2846                    .bounding_range()
2847                    .is_none_or(|(lowest, _highest)| lowest == Bound::Unbounded);
2848                let strategy_lowest = matches!(
2849                    resolution_strategy,
2850                    ResolutionStrategy::Lowest | ResolutionStrategy::LowestDirect(..)
2851                );
2852
2853                if !has_url && missing_lower_bound && strategy_lowest {
2854                    let name = package.name_no_root().unwrap();
2855                    // Handle cases where a package is listed both without and with a lower bound.
2856                    // Example:
2857                    // ```
2858                    // "coverage[toml] ; python_version < '3.11'",
2859                    // "coverage >= 7.10.0",
2860                    // ```
2861                    let bound_on_other_package = dependencies.iter().any(|other| {
2862                        Some(name) == other.package.name()
2863                            && !other
2864                                .version
2865                                .bounding_range()
2866                                .is_none_or(|(lowest, _highest)| lowest == Bound::Unbounded)
2867                    });
2868
2869                    if !bound_on_other_package {
2870                        warn_user_once!(
2871                            "The direct dependency `{name}` is unpinned. \
2872                            Consider setting a lower bound when using `--resolution lowest` \
2873                            or `--resolution lowest-direct` to avoid using outdated versions.",
2874                        );
2875                    }
2876                }
2877            }
2878
2879            // Update the package priorities.
2880            self.priorities.insert(package, version, &self.fork_urls);
2881            // As we're adding an incompatibility from the proxy package to the base package,
2882            // we need to register the base package.
2883            if let Some(base_package) = package.base_package() {
2884                self.priorities
2885                    .insert(&base_package, version, &self.fork_urls);
2886            }
2887        }
2888
2889        Ok(())
2890    }
2891
2892    /// Adds the dependencies for the selected version of the current package.
2893    ///
2894    /// For registry packages, the depending version is widened across gaps containing no other
2895    /// known version before its incompatibilities are added. Packages without a complete registry
2896    /// version map retain the selected version's singleton range.
2897    fn add_package_version_dependencies<InstalledPackages: InstalledPackagesProvider>(
2898        &mut self,
2899        for_package: Id<PubGrubPackage>,
2900        for_version: &Version,
2901        dependencies: Vec<PubGrubDependency>,
2902        index: &InMemoryIndex,
2903        installed_packages: &InstalledPackages,
2904    ) {
2905        for dependency in &dependencies {
2906            let PubGrubDependency {
2907                package,
2908                version,
2909                parent: _,
2910                source: _,
2911            } = dependency;
2912
2913            let Some(base_package) = package.base_package() else {
2914                continue;
2915            };
2916
2917            let proxy_package = self.pubgrub.package_store.alloc(package.clone());
2918            let base_package_id = self.pubgrub.package_store.alloc(base_package.clone());
2919            self.pubgrub.add_proxy_package_incompatibility(
2920                proxy_package,
2921                base_package_id,
2922                version.clone(),
2923            );
2924        }
2925
2926        // Widen across gaps so rejected adjacent versions merge into contiguous ranges rather
2927        // than leaving one hole per version.
2928        let versions = self.widen_version_to_gap(for_version, index, installed_packages);
2929        let conflict = self.pubgrub.add_package_version_dependencies(
2930            self.next,
2931            for_version.clone(),
2932            versions,
2933            dependencies.into_iter().map(|dependency| {
2934                let PubGrubDependency {
2935                    package,
2936                    version,
2937                    parent: _,
2938                    source: _,
2939                } = dependency;
2940                (package, version)
2941            }),
2942        );
2943
2944        // Conflict tracking: If the version was rejected due to its dependencies, record culprit
2945        // and affected.
2946        if let Some(incompatibility) = conflict {
2947            self.record_conflict(for_package, Some(for_version), incompatibility);
2948        }
2949    }
2950
2951    /// Widens a version of the current package to the gap around it in the known versions.
2952    fn widen_version_to_gap<InstalledPackages: InstalledPackagesProvider>(
2953        &mut self,
2954        version: &Version,
2955        index: &InMemoryIndex,
2956        installed_packages: &InstalledPackages,
2957    ) -> Range<Version> {
2958        widen_to_gap(
2959            version,
2960            self.known_versions.get_or_update(
2961                index,
2962                installed_packages,
2963                PackageSource::from_fork(
2964                    &self.pubgrub.package_store[self.next],
2965                    &self.fork_urls,
2966                    &self.fork_indexes,
2967                ),
2968                &self.pubgrub.package_store[self.next],
2969            ),
2970        )
2971    }
2972
2973    fn record_conflict(
2974        &mut self,
2975        affected: Id<PubGrubPackage>,
2976        version: Option<&Version>,
2977        incompatibility: IncompId<PubGrubPackage, Range<Version>, UnavailableReason>,
2978    ) {
2979        let mut culprit_is_real = false;
2980        for (incompatible, _term) in self.pubgrub.incompatibility_store[incompatibility].iter() {
2981            if incompatible == affected {
2982                continue;
2983            }
2984            if self.pubgrub.package_store[affected].name()
2985                == self.pubgrub.package_store[incompatible].name()
2986            {
2987                // Don't track conflicts between a marker package and the main package, when the
2988                // marker is "copying" the obligations from the main package through conflicts.
2989                continue;
2990            }
2991            culprit_is_real = true;
2992            let culprit_count = self
2993                .conflict_tracker
2994                .culprit
2995                .entry(incompatible)
2996                .or_default();
2997            *culprit_count += 1;
2998            if *culprit_count == CONFLICT_THRESHOLD {
2999                self.conflict_tracker.deprioritize.push(incompatible);
3000            }
3001        }
3002        // Don't track conflicts between a marker package and the main package, when the
3003        // marker is "copying" the obligations from the main package through conflicts.
3004        if culprit_is_real {
3005            if tracing::enabled!(Level::DEBUG) {
3006                let incompatibility = self.pubgrub.incompatibility_store[incompatibility]
3007                    .iter()
3008                    .map(|(package, _term)| &self.pubgrub.package_store[package])
3009                    .join(", ");
3010                if let Some(version) = version {
3011                    debug!(
3012                        "Recording dependency conflict of {}=={} from incompatibility of ({})",
3013                        self.pubgrub.package_store[affected], version, incompatibility
3014                    );
3015                } else {
3016                    debug!(
3017                        "Recording unit propagation conflict of {} from incompatibility of ({})",
3018                        self.pubgrub.package_store[affected], incompatibility
3019                    );
3020                }
3021            }
3022
3023            let affected_count = self.conflict_tracker.affected.entry(self.next).or_default();
3024            *affected_count += 1;
3025            if *affected_count == CONFLICT_THRESHOLD {
3026                self.conflict_tracker.prioritize.push(self.next);
3027            }
3028        }
3029    }
3030
3031    /// Change the priority of often conflicting packages and backtrack.
3032    ///
3033    /// To be called after unit propagation.
3034    fn reprioritize_conflicts(&mut self) {
3035        for package in self.conflict_tracker.prioritize.drain(..) {
3036            let changed = self
3037                .priorities
3038                .mark_conflict_early(&self.pubgrub.package_store[package]);
3039            if changed {
3040                debug!(
3041                    "Package {} has too many conflicts (affected), prioritizing",
3042                    &self.pubgrub.package_store[package]
3043                );
3044            } else {
3045                debug!(
3046                    "Package {} has too many conflicts (affected), already {:?}",
3047                    self.pubgrub.package_store[package],
3048                    self.priorities.get(&self.pubgrub.package_store[package])
3049                );
3050            }
3051        }
3052
3053        for package in self.conflict_tracker.deprioritize.drain(..) {
3054            let changed = self
3055                .priorities
3056                .mark_conflict_late(&self.pubgrub.package_store[package]);
3057            if changed {
3058                debug!(
3059                    "Package {} has too many conflicts (culprit), deprioritizing and backtracking",
3060                    self.pubgrub.package_store[package],
3061                );
3062                let backtrack_level = self.pubgrub.backtrack_package(package);
3063                if let Some(backtrack_level) = backtrack_level {
3064                    debug!("Backtracked {backtrack_level} decisions");
3065                } else {
3066                    debug!(
3067                        "Package {} is not decided, cannot backtrack",
3068                        self.pubgrub.package_store[package]
3069                    );
3070                }
3071            } else {
3072                debug!(
3073                    "Package {} has too many conflicts (culprit), already {:?}",
3074                    self.pubgrub.package_store[package],
3075                    self.priorities.get(&self.pubgrub.package_store[package])
3076                );
3077            }
3078        }
3079    }
3080
3081    /// Records that a version cannot be used.
3082    ///
3083    /// The rejected version is widened to the gap around it, so that a run of rejected versions
3084    /// excludes one contiguous range. The gap holds no known version but the rejected one, and
3085    /// none at all when that version is itself unknown: `--exclude-newer` drops a version whose
3086    /// files carry no upload time from [`KnownVersions::get_or_update`], but every one of its
3087    /// distributions is incompatible, so it cannot be selected either.
3088    fn add_unavailable_version<InstalledPackages: InstalledPackagesProvider>(
3089        &mut self,
3090        version: Version,
3091        reason: UnavailableVersion,
3092        index: &InMemoryIndex,
3093        installed_packages: &InstalledPackages,
3094    ) {
3095        let versions = self.widen_version_to_gap(&version, index, installed_packages);
3096
3097        // Incompatible requires-python versions are special in that we track
3098        // them as incompatible dependencies instead of marking the package version
3099        // as unavailable directly.
3100        if let UnavailableVersion::IncompatibleDist(
3101            IncompatibleDist::Source(IncompatibleSource::RequiresPython(requires_python, kind))
3102            | IncompatibleDist::Wheel(IncompatibleWheel::RequiresPython(requires_python, kind)),
3103        ) = reason
3104        {
3105            let package = &self.next;
3106            let python = self.pubgrub.package_store.alloc(PubGrubPackage::from(
3107                PubGrubPackageInner::Python(match kind {
3108                    PythonRequirementKind::Installed => PubGrubPython::Installed,
3109                    PythonRequirementKind::Target => PubGrubPython::Target,
3110                }),
3111            ));
3112            self.pubgrub
3113                .add_incompatibility(Incompatibility::from_dependency(
3114                    *package,
3115                    versions,
3116                    (
3117                        python,
3118                        Range::from_versions(release_specifiers_to_ranges(requires_python)),
3119                    ),
3120                ));
3121            self.pubgrub
3122                .partial_solution
3123                .add_decision(self.next, version);
3124            return;
3125        }
3126        self.pubgrub
3127            .add_incompatibility(Incompatibility::custom_term(
3128                self.next,
3129                Term::Positive(versions),
3130                UnavailableReason::Version(reason),
3131            ));
3132    }
3133
3134    fn with_continuation(mut self, continuation: ForkContinuation) -> Self {
3135        self.continuation = continuation;
3136        self
3137    }
3138
3139    /// Narrow the environment and Python requirement, invalidating candidates from the parent fork.
3140    fn with_env(mut self, env: ResolverEnvironment) -> Self {
3141        self.selected_versions.clear();
3142        self.env = env;
3143        // If the fork contains a narrowed Python requirement, apply it.
3144        if let Some(req) = self.env.narrow_python_requirement(&self.python_requirement) {
3145            debug!("Narrowed `requires-python` bound to: {}", req.target());
3146            self.python_requirement = req;
3147        }
3148        self
3149    }
3150
3151    /// Returns the URL or index for a package and version.
3152    ///
3153    /// In practice, exactly one of the returned values will be `Some`.
3154    fn source(
3155        &self,
3156        name: &PackageName,
3157        version: &Version,
3158    ) -> (Option<&VerbatimParsedUrl>, Option<&IndexUrl>) {
3159        let url = self.fork_urls.get(name);
3160        let index = url
3161            .is_none()
3162            .then(|| {
3163                self.pins
3164                    .get(name, version)
3165                    .expect("Every package should be pinned")
3166                    .index()
3167            })
3168            .flatten();
3169        (url, index)
3170    }
3171
3172    fn into_resolution(self) -> Resolution<'index> {
3173        let solution: FxHashMap<_, _> = self.pubgrub.partial_solution.extract_solution().collect();
3174        // Node order can affect diagnostic order and which artifact hashes `requirements.txt`
3175        // emits when it merges the same package version from different sources.
3176        let nodes: FxHashMap<_, _> = solution
3177            .iter()
3178            .filter_map(|(&package, version)| {
3179                if let PubGrubPackageInner::Package {
3180                    name,
3181                    kind,
3182                    marker: MarkerTree::TRUE,
3183                } = &*self.pubgrub.package_store[package]
3184                {
3185                    let (url, index) = self.source(name, version);
3186                    Some((
3187                        ResolutionPackage {
3188                            name: name.clone(),
3189                            kind: kind.clone(),
3190                            url: url.cloned(),
3191                            index: index.cloned(),
3192                        },
3193                        version.clone(),
3194                    ))
3195                } else {
3196                    None
3197                }
3198            })
3199            .collect();
3200        let nodes: Vec<_> = nodes.into_iter().collect();
3201        let node_indices: FxHashMap<_, _> = nodes
3202            .iter()
3203            .enumerate()
3204            .map(|(index, (package, version))| {
3205                (
3206                    (
3207                        &package.name,
3208                        &package.kind,
3209                        package.url.as_ref(),
3210                        package.index.as_ref(),
3211                        version,
3212                    ),
3213                    ResolutionNodeId(index),
3214                )
3215            })
3216            .collect();
3217
3218        let edge_count: usize = solution
3219            .keys()
3220            .map(|package| self.pubgrub.incompatibilities[package].len())
3221            .sum();
3222        let mut edges: Vec<ResolutionDependencyEdge> = Vec::with_capacity(edge_count);
3223        for (package, self_version) in &solution {
3224            let (self_name, self_kind) = match &*self.pubgrub.package_store[*package] {
3225                PubGrubPackageInner::Package { name, kind, .. } => (Some(name), kind),
3226                PubGrubPackageInner::Root(_) => (None, &PackageNodeKind::Base),
3227                PubGrubPackageInner::Python(_)
3228                | PubGrubPackageInner::System(_)
3229                | PubGrubPackageInner::Marker { .. }
3230                | PubGrubPackageInner::Extra { .. }
3231                | PubGrubPackageInner::Group { .. } => continue,
3232            };
3233            let from = self_name.map(|name| {
3234                let (url, index) = self.source(name, self_version);
3235                node_indices[&(name, self_kind, url, index, self_version)]
3236            });
3237            for id in &self.pubgrub.incompatibilities[package] {
3238                let incompatibility = &self.pubgrub.incompatibility_store[*id];
3239                let pubgrub::Kind::FromDependencyOf(self_package, dependency_package) =
3240                    &incompatibility.kind
3241                else {
3242                    continue;
3243                };
3244                let (self_package, dependency_package) = (*self_package, *dependency_package);
3245                let Some((self_range, dependency_range)) =
3246                    incompatibility.dependency_version_sets()
3247                else {
3248                    continue;
3249                };
3250                let dependency_range =
3251                    dependency_range.map_or_else(|| Cow::Owned(Range::empty()), Cow::Borrowed);
3252                if *package != self_package {
3253                    continue;
3254                }
3255                if !self_range.contains(self_version) {
3256                    continue;
3257                }
3258                let Some(dependency_version) = solution.get(&dependency_package) else {
3259                    continue;
3260                };
3261                if !dependency_range.contains(dependency_version) {
3262                    continue;
3263                }
3264
3265                let dependency_package = &self.pubgrub.package_store[dependency_package];
3266
3267                let (name, kind, marker) = match &**dependency_package {
3268                    PubGrubPackageInner::Package { name, kind, marker } => {
3269                        debug_assert!(kind.is_base(), "Packages should depend on a variant proxy");
3270
3271                        // Ignore self-dependencies (e.g., `tensorflow-macos` depends on `tensorflow-macos`),
3272                        // but allow groups to depend on other groups, or on the package itself.
3273                        if self_kind.group().is_none() && self_name == Some(name) {
3274                            continue;
3275                        }
3276                        (name, kind.clone(), *marker)
3277                    }
3278                    PubGrubPackageInner::Marker { name, marker } => {
3279                        if self_kind.group().is_none() && self_name == Some(name) {
3280                            continue;
3281                        }
3282                        (name, PackageNodeKind::Base, *marker)
3283                    }
3284                    PubGrubPackageInner::Extra {
3285                        name,
3286                        extra,
3287                        marker,
3288                    } => {
3289                        if self_kind.group().is_none() {
3290                            debug_assert!(self_name != Some(name), "Extras should be flattened");
3291                        }
3292                        (name, PackageNodeKind::Extra(extra.clone()), *marker)
3293                    }
3294                    PubGrubPackageInner::Group {
3295                        name,
3296                        group,
3297                        marker,
3298                    } => {
3299                        debug_assert!(self_name != Some(name), "Groups should be flattened");
3300                        (name, PackageNodeKind::Group(group.clone()), *marker)
3301                    }
3302                    PubGrubPackageInner::Root(_)
3303                    | PubGrubPackageInner::Python(_)
3304                    | PubGrubPackageInner::System(_) => continue,
3305                };
3306                let (url, index) = self.source(name, dependency_version);
3307                edges.push(ResolutionDependencyEdge {
3308                    from,
3309                    to: node_indices[&(name, &kind, url, index, dependency_version)],
3310                    marker,
3311                });
3312
3313                // An extra proxy requires both the extra and its base package. A group proxy
3314                // only requires the group itself.
3315                if let PubGrubPackageInner::Extra { .. } = &**dependency_package {
3316                    edges.push(ResolutionDependencyEdge {
3317                        from,
3318                        to: node_indices
3319                            [&(name, &PackageNodeKind::Base, url, index, dependency_version)],
3320                        marker,
3321                    });
3322                }
3323            }
3324        }
3325
3326        Resolution {
3327            nodes,
3328            edges,
3329            pins: self.pins,
3330            env: self.env,
3331        }
3332    }
3333}
3334
3335/// Widens a single version to the largest interval that contains no other known version
3336/// ([`Ranges::widen_versions`]).
3337///
3338/// The interval adds only versions the registry does not list, which can never be selected, so an
3339/// incompatibility recorded for it holds for the same selectable versions.
3340///
3341/// Returns the singleton range when the known versions are unavailable, as for a URL or workspace
3342/// package, and for an empty list, which would otherwise widen to the full range.
3343fn widen_to_gap(version: &Version, known_versions: Option<&[Version]>) -> Range<Version> {
3344    let versions = Range::singleton(version.clone());
3345    match known_versions {
3346        Some(known_versions) if !known_versions.is_empty() => {
3347            versions.widen_versions(known_versions)
3348        }
3349        _ => versions,
3350    }
3351}
3352
3353/// Fetch the metadata for an item
3354#[derive(Debug)]
3355#[expect(clippy::large_enum_variant)]
3356pub(crate) enum Request {
3357    /// A request to fetch the metadata for a package.
3358    Package(PackageName, Option<IndexMetadata>),
3359    /// A request to fetch the metadata for a built or source distribution.
3360    Dist(Dist),
3361    /// A request to fetch the metadata from an already-installed distribution.
3362    Installed(InstalledDist),
3363    /// A request to fetch variant metadata for a selected version.
3364    VariantsJson(RegistryVariantsJson),
3365    /// A request to pre-fetch the metadata for a package and the best-guess distribution.
3366    Prefetch(PackageName, Range<Version>, PythonRequirement),
3367}
3368
3369impl<'a> From<ResolvedDistRef<'a>> for Request {
3370    fn from(dist: ResolvedDistRef<'a>) -> Self {
3371        // N.B. This is almost identical to `ResolvedDistRef::to_owned`, but
3372        // creates a `Request` instead of a `ResolvedDist`. There's probably
3373        // some room for DRYing this up a bit. The obvious way would be to
3374        // add a method to create a `Dist`, but a `Dist` cannot be represented
3375        // as an installed dist.
3376        match dist {
3377            ResolvedDistRef::InstallableRegistrySourceDist { sdist, prioritized } => {
3378                // This is okay because we're only here if the prioritized dist
3379                // has an sdist, so this always succeeds.
3380                let source = prioritized.source_dist().expect("a source distribution");
3381                assert_eq!(
3382                    (&sdist.name, &sdist.version),
3383                    (&source.name, &source.version),
3384                    "expected chosen sdist to match prioritized sdist"
3385                );
3386                Self::Dist(Dist::Source(SourceDist::Registry(source)))
3387            }
3388            ResolvedDistRef::InstallableRegistryBuiltDist {
3389                wheel, prioritized, ..
3390            } => {
3391                assert_eq!(
3392                    Some(&wheel.filename),
3393                    prioritized.best_wheel().map(|(wheel, _)| &wheel.filename),
3394                    "expected chosen wheel to match best wheel"
3395                );
3396                // This is okay because we're only here if the prioritized dist
3397                // has at least one wheel, so this always succeeds.
3398                let built = prioritized.built_dist().expect("at least one wheel");
3399                Self::Dist(Dist::Built(BuiltDist::Registry(built)))
3400            }
3401            ResolvedDistRef::Installed { dist } => Self::Installed(dist.clone()),
3402        }
3403    }
3404}
3405
3406impl Display for Request {
3407    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
3408        match self {
3409            Self::Package(package_name, _) => {
3410                write!(f, "Versions {package_name}")
3411            }
3412            Self::Dist(dist) => {
3413                write!(f, "Metadata `{dist}`")
3414            }
3415            Self::Installed(dist) => {
3416                write!(f, "Installed metadata `{dist}`")
3417            }
3418            Self::VariantsJson(variants_json) => {
3419                write!(f, "Variant metadata `{}`", variants_json.filename)
3420            }
3421            Self::Prefetch(package_name, range, _) => {
3422                write!(f, "Prefetch {package_name} {range}")
3423            }
3424        }
3425    }
3426}
3427
3428#[derive(Debug)]
3429#[expect(clippy::large_enum_variant)]
3430enum Response {
3431    /// The returned metadata for a package hosted on a registry.
3432    Package(PackageName, Option<IndexUrl>, VersionsResponse),
3433    /// The returned metadata for a distribution.
3434    Dist {
3435        dist: Dist,
3436        metadata: MetadataResponse,
3437    },
3438    /// The returned metadata for an already-installed distribution.
3439    Installed {
3440        dist: InstalledDist,
3441        metadata: MetadataResponse,
3442    },
3443    /// The response to a variant metadata request.
3444    VariantsJson {
3445        variants_json: RegistryVariantsJson,
3446        response: VariantsJsonResponse,
3447    },
3448}
3449
3450/// Information about the dependencies for a particular package.
3451///
3452/// This effectively distills the dependency metadata of a package down into
3453/// its pubgrub specific constituent parts: each dependency package has a range
3454/// of possible versions.
3455enum Dependencies {
3456    /// Package dependencies are not available.
3457    Unavailable(UnavailableVersion),
3458    /// Container for all available package versions.
3459    ///
3460    /// Note that in universal mode, it is possible and allowed for multiple
3461    /// `PubGrubPackage` values in this list to have the same package name.
3462    /// These conflicts are resolved via [`ForkedDependencies::from_dependencies_universal`].
3463    Available(Vec<PubGrubDependency>),
3464    /// Package metadata has a `Requires-Python` specifier that is incompatible with the target.
3465    RequiresPython(VersionSpecifiers),
3466    /// Dependencies that should never result in a fork.
3467    ///
3468    /// For example, the dependencies of a `Marker` package will have the
3469    /// same name and version, but differ according to marker expressions.
3470    /// But we never want this to result in a fork.
3471    Unforkable(Vec<PubGrubDependency>),
3472}
3473
3474/// Information about the (possibly forked) dependencies for a particular
3475/// package.
3476///
3477/// This is like `Dependencies` but with an extra variant that only occurs when
3478/// a `Dependencies` list has multiple dependency specifications with the same
3479/// name and non-overlapping marker expressions (i.e., a fork occurs).
3480#[derive(Debug)]
3481enum ForkedDependencies {
3482    /// Package dependencies are not available.
3483    Unavailable(UnavailableVersion),
3484    /// No forking occurred.
3485    ///
3486    /// This is the same as `Dependencies::Available`.
3487    Unforked(Vec<PubGrubDependency>),
3488    /// Forked containers for all available package versions.
3489    ///
3490    /// Note that there is always at least two forks. If there would
3491    /// be fewer than 2 forks, then there is no fork at all and the
3492    /// `Unforked` variant is used instead.
3493    Forked {
3494        forks: Vec<Fork>,
3495        /// The package(s) with different requirements for disjoint markers.
3496        diverging_packages: BTreeSet<PackageName>,
3497    },
3498    /// Package metadata has a `Requires-Python` specifier that is incompatible with the target.
3499    RequiresPython(VersionSpecifiers),
3500}
3501
3502impl ForkedDependencies {
3503    /// Turn a flat list of dependencies into a potential set of forked
3504    /// groups of dependencies.
3505    ///
3506    /// A fork *only* occurs when there are multiple dependencies with the same
3507    /// name *and* those dependency specifications have corresponding marker
3508    /// expressions that are completely disjoint with one another.
3509    fn from_dependencies_universal(
3510        dependencies: Dependencies,
3511        env: &ResolverEnvironment,
3512        python_requirement: &PythonRequirement,
3513        conflicts: &Conflicts,
3514    ) -> Self {
3515        let deps = match dependencies {
3516            Dependencies::Available(deps) => deps,
3517            Dependencies::Unforkable(deps) => return Self::Unforked(deps),
3518            Dependencies::RequiresPython(requires_python) => {
3519                return Self::RequiresPython(requires_python);
3520            }
3521            Dependencies::Unavailable(err) => return Self::Unavailable(err),
3522        };
3523        let mut name_to_deps: BTreeMap<PackageName, Vec<PubGrubDependency>> = BTreeMap::new();
3524        for dep in deps {
3525            let name = dep
3526                .package
3527                .name()
3528                .expect("dependency always has a name")
3529                .clone();
3530            name_to_deps.entry(name).or_default().push(dep);
3531        }
3532        let (mut forks, diverging_packages) =
3533            Self::fork(name_to_deps, env, python_requirement, conflicts);
3534        if forks.is_empty() {
3535            Self::Unforked(vec![])
3536        } else if forks.len() == 1 {
3537            Self::Unforked(forks.pop().unwrap().dependencies)
3538        } else {
3539            Self::Forked {
3540                forks,
3541                diverging_packages,
3542            }
3543        }
3544    }
3545
3546    /// Noop companion to [`ForkedDependencies::from_dependencies_universal`] for non-universal
3547    /// resolutions with a fixed marker environment.
3548    fn from_dependencies_platform_specific(dependencies: Dependencies) -> Self {
3549        match dependencies {
3550            Dependencies::Available(deps) | Dependencies::Unforkable(deps) => Self::Unforked(deps),
3551            Dependencies::RequiresPython(requires_python) => Self::RequiresPython(requires_python),
3552            Dependencies::Unavailable(err) => Self::Unavailable(err),
3553        }
3554    }
3555
3556    /// Build a list of forks determined from the dependencies of a single package.
3557    ///
3558    /// Any time a marker expression is seen that is not true for all possible
3559    /// marker environments, it is possible for it to introduce a new fork.
3560    ///
3561    /// Returns the forks discovered among the dependencies and the package(s) that
3562    /// provoked at least one additional fork.
3563    fn fork(
3564        name_to_deps: BTreeMap<PackageName, Vec<PubGrubDependency>>,
3565        env: &ResolverEnvironment,
3566        python_requirement: &PythonRequirement,
3567        conflicts: &Conflicts,
3568    ) -> (Vec<Fork>, BTreeSet<PackageName>) {
3569        let python_marker = python_requirement.to_marker_tree();
3570
3571        let mut forks = vec![Fork::new(env.clone())];
3572        let mut diverging_packages = BTreeSet::new();
3573        for (name, mut deps) in name_to_deps {
3574            assert!(!deps.is_empty(), "every name has at least one dependency");
3575            // We never fork if there's only one dependency
3576            // specification for a given package name. This particular
3577            // strategy results in a "conservative" approach to forking
3578            // that gives up correctness in some cases in exchange for
3579            // more limited forking. More limited forking results in
3580            // simpler-and-easier-to-understand lock files and faster
3581            // resolving. The correctness we give up manifests when
3582            // two transitive non-sibling dependencies conflict. In
3583            // that case, we don't detect the fork ahead of time (at
3584            // present).
3585            if let [dep] = deps.as_slice() {
3586                // There's one exception: if the requirement increases the minimum-supported Python
3587                // version, we also fork in order to respect that minimum in the subsequent
3588                // resolution.
3589                //
3590                // For example, given `requires-python = ">=3.7"` and `uv ; python_version >= "3.8"`,
3591                // where uv itself only supports Python 3.8 and later, we need to fork to ensure
3592                // that the resolution can find a solution.
3593                if marker::requires_python(dep.package.marker())
3594                    .is_none_or(|bound| !python_requirement.raises(&bound))
3595                {
3596                    let dep = deps.pop().unwrap();
3597                    let marker = dep.package.marker();
3598                    for fork in &mut forks {
3599                        if fork.env.included_by_marker(marker) {
3600                            fork.add_dependency(dep.clone());
3601                        }
3602                    }
3603                    continue;
3604                }
3605            } else {
3606                // If all dependencies have the same markers, we should also avoid forking.
3607                if let Some(dep) = deps.first() {
3608                    let marker = dep.package.marker();
3609                    if deps.iter().all(|dep| marker == dep.package.marker()) {
3610                        // Unless that "same marker" is a Python requirement that is stricter than
3611                        // the current Python requirement. In that case, we need to fork to respect
3612                        // the stricter requirement.
3613                        if marker::requires_python(marker)
3614                            .is_none_or(|bound| !python_requirement.raises(&bound))
3615                        {
3616                            for dep in deps {
3617                                for fork in &mut forks {
3618                                    if fork.env.included_by_marker(marker) {
3619                                        fork.add_dependency(dep.clone());
3620                                    }
3621                                }
3622                            }
3623                            continue;
3624                        }
3625                    }
3626                }
3627            }
3628            for dep in deps {
3629                let mut forker = match ForkingPossibility::new(env, &dep) {
3630                    ForkingPossibility::Possible(forker) => forker,
3631                    ForkingPossibility::DependencyAlwaysExcluded => {
3632                        // If the markers can never be satisfied by the parent
3633                        // fork, then we can drop this dependency unceremoniously.
3634                        continue;
3635                    }
3636                    ForkingPossibility::NoForkingPossible => {
3637                        // Or, if the markers are always true, then we just
3638                        // add the dependency to every fork unconditionally.
3639                        for fork in &mut forks {
3640                            fork.add_dependency(dep.clone());
3641                        }
3642                        continue;
3643                    }
3644                };
3645                // Otherwise, we *should* need to add a new fork...
3646                diverging_packages.insert(name.clone());
3647
3648                let mut new = vec![];
3649                for fork in std::mem::take(&mut forks) {
3650                    let Some((remaining_forker, envs)) = forker.fork(&fork.env) else {
3651                        new.push(fork);
3652                        continue;
3653                    };
3654                    forker = remaining_forker;
3655
3656                    for fork_env in envs {
3657                        let mut new_fork = fork.clone();
3658                        new_fork.set_env(fork_env);
3659                        // We only add the dependency to this fork if it
3660                        // satisfies the fork's markers. Some forks are
3661                        // specifically created to exclude this dependency,
3662                        // so this isn't always true!
3663                        if forker.included(&new_fork.env) {
3664                            new_fork.add_dependency(dep.clone());
3665                        }
3666                        // Filter out any forks we created that are disjoint with our
3667                        // Python requirement.
3668                        if new_fork.env.included_by_marker(python_marker) {
3669                            new.push(new_fork);
3670                        }
3671                    }
3672                }
3673                forks = new;
3674            }
3675        }
3676        // When there is a conflicting group configuration, we need
3677        // to potentially add more forks. Each fork added contains an
3678        // exclusion list of conflicting groups where dependencies with
3679        // the corresponding package and extra name are forcefully
3680        // excluded from that group.
3681        //
3682        // We specifically iterate on conflicting groups and
3683        // potentially re-generate all forks for each one. We do it
3684        // this way in case there are multiple sets of conflicting
3685        // groups that impact the forks here.
3686        //
3687        // For example, if we have conflicting groups {x1, x2} and {x3,
3688        // x4}, we need to make sure the forks generated from one set
3689        // also account for the other set.
3690        for set in conflicts.iter() {
3691            let mut new = vec![];
3692            for fork in std::mem::take(&mut forks) {
3693                // Check if this conflict set is relevant to this fork. We need two conditions:
3694                //
3695                // 1. At least one item has dependencies in this fork (otherwise there's nothing to
3696                //    fork on).
3697                // 2. At least two items are not already excluded in this fork's environment
3698                //    (otherwise the conflict constraint is already satisfied and no fork is
3699                //    needed).
3700                let mut has_conflicting_dependency = false;
3701                for item in set.iter() {
3702                    if fork.contains_conflicting_item(item.as_ref()) {
3703                        has_conflicting_dependency = true;
3704                        diverging_packages.insert(item.package().clone());
3705                        break;
3706                    }
3707                }
3708                if !has_conflicting_dependency {
3709                    new.push(fork);
3710                    continue;
3711                }
3712
3713                // If fewer than two items in this conflict set are still possible (not already
3714                // excluded) in this fork, the conflict constraint is already satisfied by prior
3715                // forking. We can skip the full N+1 fork split if the single remaining non-excluded
3716                // item doesn't appear in any other conflict set (since it would never need its own
3717                // "excluded" variant).
3718                let non_excluded: Vec<_> = set
3719                    .iter()
3720                    .filter(|item| fork.env.included_by_group(item.as_ref()))
3721                    .collect();
3722                if non_excluded.len() < 2 {
3723                    // Check if any non-excluded item still has a live conflict in another set —
3724                    // i.e., another set where this item AND at least one other non-excluded item
3725                    // both appear. If so, we still need to fork to create the "excluded" variant
3726                    // for that item.
3727                    let dominated = non_excluded.iter().all(|item| {
3728                        !conflicts.iter().any(|other_set| {
3729                            !std::ptr::eq(set, other_set)
3730                                && other_set.contains(item.package(), item.kind().as_ref())
3731                                && other_set
3732                                    .iter()
3733                                    .filter(|other_item| {
3734                                        other_item.package() != item.package()
3735                                            || other_item.kind() != item.kind()
3736                                    })
3737                                    .any(|other_item| {
3738                                        fork.env.included_by_group(other_item.as_ref())
3739                                    })
3740                        })
3741                    });
3742                    if dominated {
3743                        // When dependencies are added to forks, we check `included_by_marker` but
3744                        // not on whether the dependency's conflict item is included by the fork's
3745                        // environment so there may be extraneous dependencies and we need to filter
3746                        // the fork to clean up dependencies gated on already-excluded extras.
3747                        let rules: Vec<_> = set
3748                            .iter()
3749                            .filter(|item| !fork.env.included_by_group(item.as_ref()))
3750                            .cloned()
3751                            .map(Err)
3752                            .collect();
3753                        if let Some(filtered) = fork.filter(rules) {
3754                            new.push(filtered);
3755                        }
3756                        continue;
3757                    }
3758                }
3759
3760                // Create a fork that excludes ALL conflicts.
3761                if let Some(fork_none) = fork.clone().filter(set.iter().cloned().map(Err)) {
3762                    new.push(fork_none);
3763                }
3764
3765                // Now create a fork for each conflicting group, where
3766                // that fork excludes every *other* conflicting group.
3767                //
3768                // So if we have conflicting extras foo, bar and baz,
3769                // then this creates three forks: one that excludes
3770                // {foo, bar}, one that excludes {foo, baz} and one
3771                // that excludes {bar, baz}.
3772                for (i, _) in set.iter().enumerate() {
3773                    let fork_allows_group = fork.clone().filter(
3774                        set.iter()
3775                            .cloned()
3776                            .enumerate()
3777                            .map(|(j, group)| if i == j { Ok(group) } else { Err(group) }),
3778                    );
3779                    if let Some(fork_allows_group) = fork_allows_group {
3780                        new.push(fork_allows_group);
3781                    }
3782                }
3783            }
3784            forks = new;
3785        }
3786        (forks, diverging_packages)
3787    }
3788}
3789
3790/// A single fork in a list of dependencies.
3791///
3792/// A fork corresponds to the full list of dependencies for a package,
3793/// but with any conflicting dependency specifications omitted. For
3794/// example, if we have `a<2 ; sys_platform == 'foo'` and `a>=2 ;
3795/// sys_platform == 'bar'`, then because the dependency specifications
3796/// have the same name and because the marker expressions are disjoint,
3797/// a fork occurs. One fork will contain `a<2` but not `a>=2`, while
3798/// the other fork will contain `a>=2` but not `a<2`.
3799#[derive(Clone, Debug)]
3800struct Fork {
3801    /// The list of dependencies for this fork, guaranteed to be conflict
3802    /// free. (i.e., There are no two packages with the same name with
3803    /// non-overlapping marker expressions.)
3804    ///
3805    /// Note that callers shouldn't mutate this sequence directly. Instead,
3806    /// they should use `add_forked_package` or `add_nonfork_package`. Namely,
3807    /// it should be impossible for a package with a marker expression that is
3808    /// disjoint from the marker expression on this fork to be added.
3809    dependencies: Vec<PubGrubDependency>,
3810    /// The conflicting groups in this fork.
3811    ///
3812    /// This exists to make some access patterns more efficient. Namely,
3813    /// it makes it easy to check whether there's a dependency with a
3814    /// particular conflicting group in this fork.
3815    conflicts: crate::FxHashbrownSet<ConflictItem>,
3816    /// The resolver environment for this fork.
3817    ///
3818    /// Principally, this corresponds to the markers in this for. So in the
3819    /// example above, the `a<2` fork would have `sys_platform == 'foo'`, while
3820    /// the `a>=2` fork would have `sys_platform == 'bar'`.
3821    ///
3822    /// If this fork was generated from another fork, then this *includes*
3823    /// the criteria from its parent. i.e., Its marker expression represents
3824    /// the intersection of the marker expression from its parent and any
3825    /// additional marker expression generated by addition forking based on
3826    /// conflicting dependency specifications.
3827    env: ResolverEnvironment,
3828}
3829
3830impl Fork {
3831    /// Create a new fork with no dependencies with the given resolver
3832    /// environment.
3833    fn new(env: ResolverEnvironment) -> Self {
3834        Self {
3835            dependencies: vec![],
3836            conflicts: crate::FxHashbrownSet::default(),
3837            env,
3838        }
3839    }
3840
3841    /// Add a dependency to this fork.
3842    fn add_dependency(&mut self, dep: PubGrubDependency) {
3843        if let Some(conflicting_item) = dep.conflicting_item() {
3844            self.conflicts.insert(conflicting_item.to_owned());
3845        }
3846        self.dependencies.push(dep);
3847    }
3848
3849    /// Sets the resolver environment to the one given.
3850    ///
3851    /// Any dependency in this fork that does not satisfy the given environment
3852    /// is removed.
3853    fn set_env(&mut self, env: ResolverEnvironment) {
3854        self.env = env;
3855        self.dependencies.retain(|dep| {
3856            let marker = dep.package.marker();
3857            if self.env.included_by_marker(marker) {
3858                return true;
3859            }
3860            if let Some(conflicting_item) = dep.conflicting_item() {
3861                self.conflicts.remove(&conflicting_item);
3862            }
3863            false
3864        });
3865    }
3866
3867    /// Returns true if any of the dependencies in this fork contain a
3868    /// dependency with the given package and extra values.
3869    fn contains_conflicting_item(&self, item: ConflictItemRef<'_>) -> bool {
3870        self.conflicts.contains(&item)
3871    }
3872
3873    /// Include or Exclude the given groups from this fork.
3874    ///
3875    /// This removes all dependencies matching the given conflicting groups.
3876    ///
3877    /// If the exclusion rules would result in a fork with an unsatisfiable
3878    /// resolver environment, then this returns `None`.
3879    fn filter(
3880        mut self,
3881        rules: impl IntoIterator<Item = Result<ConflictItem, ConflictItem>>,
3882    ) -> Option<Self> {
3883        self.env = self.env.filter_by_group(rules)?;
3884        self.dependencies.retain(|dep| {
3885            let Some(conflicting_item) = dep.conflicting_item() else {
3886                return true;
3887            };
3888            if self.env.included_by_group(conflicting_item) {
3889                return true;
3890            }
3891            match conflicting_item.kind() {
3892                // We should not filter entire projects unless they're a top-level dependency
3893                // Otherwise, we'll fail to solve for children of the project, like extras
3894                ConflictKindRef::Project => {
3895                    if dep.parent.is_some() {
3896                        return true;
3897                    }
3898                }
3899                ConflictKindRef::Group(_) => {}
3900                ConflictKindRef::Extra(_) => {}
3901            }
3902            self.conflicts.remove(&conflicting_item);
3903            false
3904        });
3905        Some(self)
3906    }
3907
3908    /// Compare forks by their lower `requires-python` bounds.
3909    fn cmp_requires_python(&self, other: &Self) -> Ordering {
3910        cmp_requires_python(&self.env, &other.env)
3911    }
3912
3913    /// Compare forks, preferring forks with upper bounds.
3914    fn cmp_upper_bounds(&self, other: &Self) -> Ordering {
3915        // We'd prefer to solve `numpy <= 2` before solving `numpy >= 1`, since the resolution
3916        // produced by the former might work for the latter, but the inverse is unlikely to be true
3917        // due to maximum version selection. (Selecting `numpy==2.0.0` would satisfy both forks, but
3918        // selecting the latest `numpy` would not.)
3919        let self_upper_bounds = self
3920            .dependencies
3921            .iter()
3922            .filter(|dep| {
3923                dep.version
3924                    .bounding_range()
3925                    .is_some_and(|(_, upper)| !matches!(upper, Bound::Unbounded))
3926            })
3927            .count();
3928        let other_upper_bounds = other
3929            .dependencies
3930            .iter()
3931            .filter(|dep| {
3932                dep.version
3933                    .bounding_range()
3934                    .is_some_and(|(_, upper)| !matches!(upper, Bound::Unbounded))
3935            })
3936            .count();
3937
3938        self_upper_bounds.cmp(&other_upper_bounds)
3939    }
3940}
3941
3942/// Compare resolver environments by their lower Python bounds.
3943fn cmp_requires_python(
3944    self_env: &ResolverEnvironment,
3945    other_env: &ResolverEnvironment,
3946) -> Ordering {
3947    // A higher `requires-python` requirement indicates a _higher-priority_ fork.
3948    //
3949    // This ordering ensures that we prefer choosing the highest version for each fork based on
3950    // its `requires-python` requirement.
3951    //
3952    // The reverse would prefer choosing fewer versions, at the cost of using older package
3953    // versions on newer Python versions. For example, if reversed, we'd prefer to solve `<3.7
3954    // before solving `>=3.7`, since the resolution produced by the former might work for the
3955    // latter, but the inverse is unlikely to be true.
3956    let self_bound = self_env.requires_python().unwrap_or_default();
3957    let other_bound = other_env.requires_python().unwrap_or_default();
3958    self_bound.lower().cmp(other_bound.lower())
3959}
3960
3961impl Eq for Fork {}
3962
3963impl PartialEq for Fork {
3964    fn eq(&self, other: &Self) -> bool {
3965        self.dependencies == other.dependencies && self.env == other.env
3966    }
3967}
3968
3969#[derive(Debug, Clone)]
3970pub(crate) struct VersionFork {
3971    /// The environment to use in the fork.
3972    env: ResolverEnvironment,
3973    /// The initial package to select in the fork.
3974    id: Id<PubGrubPackage>,
3975    /// The initial version to set for the selected package in the fork.
3976    version: Option<Version>,
3977}
3978
3979/// Enrich a [`ResolveError`] with additional information about why a given package was included.
3980fn enrich_dependency_error(
3981    error: ResolveError,
3982    id: Id<PubGrubPackage>,
3983    version: &Version,
3984    pubgrub: &State<UvDependencyProvider>,
3985) -> ResolveError {
3986    let Some(name) = pubgrub.package_store[id].name_no_root() else {
3987        return error;
3988    };
3989    let chain = DerivationChainBuilder::from_state(id, version, pubgrub).unwrap_or_default();
3990    ResolveError::Dependencies(Box::new(error), name.clone(), version.clone(), chain)
3991}
3992
3993/// Compute the set of markers for which a package is known to be relevant.
3994fn find_environments(id: Id<PubGrubPackage>, state: &State<UvDependencyProvider>) -> MarkerTree {
3995    let package = &state.package_store[id];
3996    if package.is_root() {
3997        return MarkerTree::TRUE;
3998    }
3999
4000    // First, collect the reverse-dependency closure for the package. We limit the propagation
4001    // below to this subgraph so cycles in unrelated packages don't matter here.
4002    let mut ancestors = FxHashSet::default();
4003    let mut stack = vec![id];
4004    let mut root = None;
4005    ancestors.insert(id);
4006
4007    while let Some(current) = stack.pop() {
4008        let Some(incompatibilities) = state.incompatibilities.get(&current) else {
4009            continue;
4010        };
4011
4012        for index in incompatibilities {
4013            let incompat = &state.incompatibility_store[*index];
4014            if let Kind::FromDependencyOf(parent, child) = &incompat.kind {
4015                if current != *child {
4016                    continue;
4017                }
4018                if ancestors.insert(*parent) {
4019                    if state.package_store[*parent].is_root() {
4020                        root = Some(*parent);
4021                    }
4022                    stack.push(*parent);
4023                }
4024            }
4025        }
4026    }
4027
4028    let Some(root) = root else {
4029        return MarkerTree::FALSE;
4030    };
4031
4032    // Propagate markers forward from the root through the collected subgraph. This reaches a
4033    // fixpoint even in the presence of cycles, unlike the recursive reverse walk above.
4034    let mut environments = FxHashMap::default();
4035    let mut queue = VecDeque::from([root]);
4036    environments.insert(root, MarkerTree::TRUE);
4037
4038    while let Some(current) = queue.pop_front() {
4039        let Some(current_environment) = environments.get(&current).copied() else {
4040            continue;
4041        };
4042        let Some(incompatibilities) = state.incompatibilities.get(&current) else {
4043            continue;
4044        };
4045
4046        for index in incompatibilities {
4047            let incompat = &state.incompatibility_store[*index];
4048            let Kind::FromDependencyOf(parent, child) = &incompat.kind else {
4049                continue;
4050            };
4051            if current != *parent || !ancestors.contains(child) {
4052                continue;
4053            }
4054
4055            let mut next_environment = state.package_store[*child].marker();
4056            next_environment = next_environment.and(current_environment);
4057
4058            let entry = environments.entry(*child).or_insert(MarkerTree::FALSE);
4059            let mut combined = *entry;
4060            combined = combined.or(next_environment);
4061            if combined != *entry {
4062                *entry = combined;
4063                queue.push_back(*child);
4064            }
4065        }
4066    }
4067
4068    environments.remove(&id).unwrap_or(MarkerTree::FALSE)
4069}
4070
4071#[derive(Debug, Default, Clone)]
4072struct ConflictTracker {
4073    /// How often a decision on the package was discarded due to another package decided earlier.
4074    affected: FxHashMap<Id<PubGrubPackage>, usize>,
4075    /// Package(s) to be prioritized after the next unit propagation
4076    ///
4077    /// Distilled from `affected` for fast checking in the hot loop.
4078    prioritize: Vec<Id<PubGrubPackage>>,
4079    /// How often a package was decided earlier and caused another package to be discarded.
4080    culprit: FxHashMap<Id<PubGrubPackage>, usize>,
4081    /// Package(s) to be de-prioritized after the next unit propagation
4082    ///
4083    /// Distilled from `culprit` for fast checking in the hot loop.
4084    deprioritize: Vec<Id<PubGrubPackage>>,
4085}
4086
4087#[cfg(test)]
4088mod tests {
4089    use super::*;
4090
4091    fn versions(versions: &[&str]) -> Vec<Version> {
4092        versions
4093            .iter()
4094            .map(|version| version.parse().expect("valid version"))
4095            .collect()
4096    }
4097
4098    #[test]
4099    fn widens_a_version_to_its_gap() {
4100        let known_versions = versions(&["1.0", "2.0", "3.0"]);
4101        let version: Version = "2.0".parse().expect("valid version");
4102
4103        // A version between two others widens to the open interval between them.
4104        assert_eq!(
4105            widen_to_gap(&version, Some(&known_versions)).to_string(),
4106            ">1.0, <3.0"
4107        );
4108
4109        // At the ends of the listing the interval is unbounded.
4110        let version: Version = "1.0".parse().expect("valid version");
4111        assert_eq!(
4112            widen_to_gap(&version, Some(&known_versions)).to_string(),
4113            "<2.0"
4114        );
4115        let version: Version = "3.0".parse().expect("valid version");
4116        assert_eq!(
4117            widen_to_gap(&version, Some(&known_versions)).to_string(),
4118            ">2.0"
4119        );
4120    }
4121
4122    #[test]
4123    fn widens_a_version_without_known_versions_to_itself() {
4124        let version: Version = "2.0".parse().expect("valid version");
4125
4126        // A URL or workspace package has no registry version map to widen against.
4127        assert_eq!(
4128            widen_to_gap(&version, None),
4129            Range::singleton(version.clone())
4130        );
4131
4132        // An empty list would otherwise widen to the full range.
4133        assert_eq!(widen_to_gap(&version, Some(&[])), Range::singleton(version));
4134    }
4135}