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