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