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