Skip to main content

uv_resolver/resolver/
mod.rs

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