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