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