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