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