Skip to main content

uv_resolver/resolver/
mod.rs

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