1use std::borrow::Cow;
4use std::cmp::Ordering;
5use std::collections::{BTreeMap, BTreeSet, VecDeque};
6use std::fmt::{Display, Formatter};
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, Term};
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::UniversalMarker;
81use crate::yanks::AllowedYanks;
82use crate::{DependencyMode, Exclusions, FlatIndex, Options, ResolutionMode, VersionMap, marker};
83pub(crate) use provider::MetadataUnavailable;
84pub(crate) use resolution::{Resolution, ResolutionDependencyEdge, ResolutionPackage};
85
86mod availability;
87mod batch_prefetch;
88mod derivation;
89mod environment;
90mod fork_map;
91mod index;
92mod indexes;
93mod provider;
94mod reporter;
95mod resolution;
96mod system;
97mod urls;
98
99const CONFLICT_THRESHOLD: usize = 5;
101
102pub struct Resolver<Provider: ResolverProvider, InstalledPackages: InstalledPackagesProvider> {
103 state: ResolverState<InstalledPackages>,
104 provider: Provider,
105}
106
107struct ResolverState<InstalledPackages: InstalledPackagesProvider> {
110 project: Option<PackageName>,
111 requirements: Vec<Requirement>,
112 constraints: Constraints,
113 overrides: Overrides,
114 excludes: Excludes,
115 preferences: Preferences,
116 git: GitResolver,
117 capabilities: IndexCapabilities,
118 locations: IndexLocations,
119 exclusions: Exclusions,
120 urls: Urls,
121 indexes: Indexes,
122 dependency_mode: DependencyMode,
123 hasher: HashStrategy,
124 env: ResolverEnvironment,
125 current_environment: MarkerEnvironment,
127 tags: Option<Tags>,
128 python_requirement: PythonRequirement,
129 conflicts: Conflicts,
130 workspace_members: BTreeSet<PackageName>,
131 selector: CandidateSelector,
132 index: InMemoryIndex,
133 installed_packages: InstalledPackages,
134 unavailable_packages: Box<HashMap<PackageName, UnavailablePackage>>,
137 incomplete_packages: Box<HashMap<PackageName, HashMap<Version, MetadataUnavailable>>>,
139 options: Options,
141 reporter: Option<Arc<dyn Reporter>>,
143}
144
145impl<'a, Context: BuildContext, InstalledPackages: InstalledPackagesProvider>
146 Resolver<DefaultResolverProvider<'a, Context>, InstalledPackages>
147{
148 pub fn new(
167 manifest: Manifest,
168 options: Options,
169 python_requirement: &'a PythonRequirement,
170 env: ResolverEnvironment,
171 current_environment: &MarkerEnvironment,
172 conflicts: Conflicts,
173 tags: Option<&'a Tags>,
174 flat_index: &'a FlatIndex,
175 index: &'a InMemoryIndex,
176 hasher: &'a HashStrategy,
177 build_context: &'a Context,
178 installed_packages: InstalledPackages,
179 database: DistributionDatabase<'a, Context>,
180 ) -> Result<Self, ResolveError> {
181 let provider = DefaultResolverProvider::new(
182 database,
183 flat_index,
184 tags,
185 python_requirement.target(),
186 AllowedYanks::from_manifest(&manifest, &env, options.dependency_mode),
187 hasher,
188 options.exclude_newer.clone(),
189 build_context.locations(),
190 build_context.build_options(),
191 build_context.capabilities(),
192 );
193
194 Ok(Self::new_custom_io(
195 manifest,
196 options,
197 hasher,
198 env,
199 current_environment,
200 tags.cloned(),
201 python_requirement,
202 conflicts,
203 index,
204 build_context.git(),
205 build_context.capabilities(),
206 build_context.locations(),
207 provider,
208 installed_packages,
209 ))
210 }
211}
212
213impl<Provider: ResolverProvider, InstalledPackages: InstalledPackagesProvider>
214 Resolver<Provider, InstalledPackages>
215{
216 pub fn new_custom_io(
218 manifest: Manifest,
219 options: Options,
220 hasher: &HashStrategy,
221 env: ResolverEnvironment,
222 current_environment: &MarkerEnvironment,
223 tags: Option<Tags>,
224 python_requirement: &PythonRequirement,
225 conflicts: Conflicts,
226 index: &InMemoryIndex,
227 git: &GitResolver,
228 capabilities: &IndexCapabilities,
229 locations: &IndexLocations,
230 provider: Provider,
231 installed_packages: InstalledPackages,
232 ) -> Self {
233 let state = ResolverState {
234 index: index.clone(),
235 git: git.clone(),
236 capabilities: capabilities.clone(),
237 selector: CandidateSelector::for_resolution(&options, &manifest, &env),
238 dependency_mode: options.dependency_mode,
239 urls: Urls::from_manifest(&manifest, &env, git, options.dependency_mode),
240 indexes: Indexes::from_manifest(&manifest, &env, options.dependency_mode),
241 project: manifest.project,
242 workspace_members: manifest.workspace_members,
243 requirements: manifest.requirements,
244 constraints: manifest.constraints,
245 overrides: manifest.overrides,
246 excludes: manifest.excludes,
247 preferences: manifest.preferences,
248 exclusions: manifest.exclusions,
249 hasher: hasher.clone(),
250 locations: locations.clone(),
251 env,
252 current_environment: current_environment.clone(),
253 tags,
254 python_requirement: python_requirement.clone(),
255 conflicts,
256 installed_packages,
257 unavailable_packages: Box::default(),
258 incomplete_packages: Box::default(),
259 options,
260 reporter: None,
261 };
262 Self { state, provider }
263 }
264
265 #[must_use]
267 pub fn with_reporter(self, reporter: Arc<dyn Reporter>) -> Self {
268 Self {
269 state: ResolverState {
270 reporter: Some(reporter.clone()),
271 ..self.state
272 },
273 provider: self
274 .provider
275 .with_reporter(reporter.into_distribution_reporter()),
276 }
277 }
278
279 pub async fn resolve(self) -> Result<ResolverOutput, ResolveError> {
281 let state = Arc::new(self.state);
282 let provider = Arc::new(self.provider);
283
284 let (request_sink, request_stream) = mpsc::channel(300);
288
289 let requests_fut = state.clone().fetch(provider.clone(), request_stream).fuse();
291
292 let solver = state.clone();
294 let (tx, rx) = oneshot::channel();
295 thread::Builder::new()
296 .name("uv-resolver".into())
297 .spawn(move || {
298 let result = solver.solve(&request_sink);
299
300 let _ = tx.send(result);
302 })
303 .unwrap();
304
305 let resolve_fut = async move { rx.await.map_err(|_| ResolveError::ChannelClosed) };
306
307 let ((), resolution) = tokio::try_join!(requests_fut, resolve_fut)?;
309
310 state.on_complete();
311 resolution
312 }
313}
314
315impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackages> {
316 #[instrument(skip_all)]
317 fn solve(
318 self: Arc<Self>,
319 request_sink: &Sender<Request>,
320 ) -> Result<ResolverOutput, ResolveError> {
321 debug!(
322 "Solving with installed Python version: {}",
323 self.python_requirement.exact()
324 );
325 debug!(
326 "Solving with target Python version: {}",
327 self.python_requirement.target()
328 );
329 if !self.options.exclude_newer.is_empty() {
330 debug!("Solving with exclude-newer: {}", self.options.exclude_newer);
331 }
332
333 let mut visited = FxHashSet::default();
334
335 let root = PubGrubPackage::from(PubGrubPackageInner::Root(self.project.clone()));
336 let pubgrub = State::init(root.clone(), MIN_VERSION.clone());
337 let prefetcher = BatchPrefetcher::new(
338 self.capabilities.clone(),
339 self.index.clone(),
340 request_sink.clone(),
341 );
342 let state = ForkState::new(
343 pubgrub,
344 self.env.clone(),
345 self.python_requirement.clone(),
346 prefetcher,
347 );
348 let mut preferences = self.preferences.clone();
349 let mut forked_states = self.env.initial_forked_states(state)?;
350
351 match (self.options.fork_strategy, self.options.resolution_mode) {
357 (ForkStrategy::Fewest, _) | (_, ResolutionMode::Lowest) => {
358 forked_states.sort_by(|a, b| cmp_requires_python(&a.env, &b.env).reverse());
359 }
360 (ForkStrategy::RequiresPython, _) => {
361 forked_states.sort_by(|a, b| cmp_requires_python(&a.env, &b.env));
362 }
363 }
364 let mut resolutions = vec![];
365
366 'FORK: while let Some(mut state) = forked_states.pop() {
367 if let Some(split) = state.env.end_user_fork_display() {
368 let requires_python = state.python_requirement.target();
369 debug!("Solving {split} (requires-python: {requires_python:?})");
370 }
371 let start = Instant::now();
372 loop {
373 let highest_priority_pkg =
374 if let Some(initial) = state.initial_id.take() {
375 initial
379 } else {
380 let result = state.pubgrub.unit_propagation(state.next);
382 match result {
383 Err(err) => {
384 return Err(self.convert_no_solution_err(
386 err,
387 state.fork_urls,
388 state.fork_indexes,
389 &state.known_versions.0,
390 state.env,
391 self.current_environment.clone(),
392 &visited,
393 ));
394 }
395 Ok(conflicts) => {
396 for (affected, incompatibility) in conflicts {
397 state.record_conflict(affected, None, incompatibility);
400 }
401 }
402 }
403
404 if self.dependency_mode.is_transitive() {
406 Self::pre_visit(
407 state.pubgrub.partial_solution.prioritized_packages().map(
408 |(id, range)| (id, &state.pubgrub.package_store[id], range),
409 ),
410 &mut state.pre_visited,
411 &self.urls,
412 &self.indexes,
413 &state.python_requirement,
414 request_sink,
415 )?;
416 }
417
418 state.reprioritize_conflicts();
419
420 trace!(
421 "Assigned packages: {}",
422 state
423 .pubgrub
424 .partial_solution
425 .extract_solution()
426 .filter(|(p, _)| !state.pubgrub.package_store[*p].is_proxy())
427 .map(|(p, v)| format!("{}=={}", state.pubgrub.package_store[p], v))
428 .join(", ")
429 );
430 let Some((highest_priority_pkg, _)) =
434 state.pubgrub.partial_solution.pick_highest_priority_pkg(
435 |id, _range| state.priorities.get(&state.pubgrub.package_store[id]),
436 )
437 else {
438 if tracing::enabled!(Level::DEBUG) {
440 state.prefetcher.log_tried_versions();
441 }
442 debug!(
443 "{} resolution took {:.3}s",
444 state.env,
445 start.elapsed().as_secs_f32()
446 );
447
448 let resolution = state.into_resolution();
449
450 if matches!(
459 self.options.resolution_mode,
460 ResolutionMode::Lowest | ResolutionMode::Highest
461 ) {
462 let marker = resolution
463 .env
464 .try_universal_markers()
465 .unwrap_or(UniversalMarker::TRUE);
466 for (package, version) in &resolution.nodes {
467 preferences.insert(
468 package.name.clone(),
469 package.index.clone(),
470 marker,
471 version.clone(),
472 PreferenceSource::Resolver,
473 );
474 }
475 }
476
477 resolutions.push(resolution);
478 continue 'FORK;
479 };
480 trace!(
481 "Chose package for decision: {}. remaining choices: {}",
482 state.pubgrub.package_store[highest_priority_pkg],
483 state
484 .pubgrub
485 .partial_solution
486 .undecided_packages()
487 .filter(|(p, _)| !state.pubgrub.package_store[**p].is_proxy())
488 .map(|(p, _)| state.pubgrub.package_store[*p].to_string())
489 .join(", ")
490 );
491
492 highest_priority_pkg
493 };
494
495 state.next = highest_priority_pkg;
496
497 let next_id = state.next;
499 let next_package = &state.pubgrub.package_store[state.next];
500
501 let url = next_package
502 .name()
503 .and_then(|name| state.fork_urls.get(name));
504 let index = next_package
505 .name()
506 .and_then(|name| state.fork_indexes.get(name));
507
508 self.request_package(next_package, url, index, request_sink)?;
520
521 let version = if let Some(version) = state.initial_version.take() {
522 version
526 } else {
527 let term_intersection = state
528 .pubgrub
529 .partial_solution
530 .term_intersection_for_package(next_id)
531 .expect("a package was chosen but we don't have a term");
532 let range = term_intersection.unwrap_positive();
533
534 let cache_selected_version = url.is_none() && index.is_none();
538 let decision = if cache_selected_version
539 && let Some((selected_range, version)) =
540 state.selected_versions.get(&next_id)
541 && selected_range == range
542 {
543 Some(ResolverVersion::Unforked(version.clone()))
544 } else {
545 let decision = self.choose_version(
546 next_package,
547 next_id,
548 index.map(IndexMetadata::url),
549 range,
550 &mut state.pins,
551 &preferences,
552 &state.fork_urls,
553 &state.env,
554 &state.python_requirement,
555 &state.pubgrub,
556 &mut visited,
557 request_sink,
558 )?;
559
560 if cache_selected_version
561 && let Some(ResolverVersion::Unforked(version)) = &decision
562 {
563 state
564 .selected_versions
565 .insert(next_id, (range.clone(), version.clone()));
566 }
567
568 decision
569 };
570
571 let Some(version) = decision else {
573 debug!("No compatible version found for: {next_package}");
574
575 let term_intersection = state
576 .pubgrub
577 .partial_solution
578 .term_intersection_for_package(next_id)
579 .expect("a package was chosen but we don't have a term");
580
581 if let PubGrubPackageInner::Package { name, .. } = &**next_package {
582 if let Some(reason) = self.unavailable_packages.pin().get(name) {
584 state
585 .pubgrub
586 .add_incompatibility(Incompatibility::custom_term(
587 next_id,
588 term_intersection.clone(),
589 UnavailableReason::Package(reason.clone()),
590 ));
591 continue;
592 }
593 }
594
595 state
596 .pubgrub
597 .add_incompatibility(Incompatibility::no_versions(
598 next_id,
599 term_intersection.clone(),
600 ));
601 continue;
602 };
603
604 let version = match version {
605 ResolverVersion::Unforked(version) => version,
606 ResolverVersion::Forked(forks) => {
607 forked_states.extend(self.version_forks_to_fork_states(state, forks));
608 continue 'FORK;
609 }
610 ResolverVersion::Unavailable(version, reason) => {
611 state.add_unavailable_version(
612 version,
613 reason,
614 &self.index,
615 &self.installed_packages,
616 );
617 continue;
618 }
619 };
620
621 if url.is_none() {
623 state.prefetcher.prefetch_batches(
624 next_package,
625 index,
626 &version,
627 term_intersection.unwrap_positive(),
628 state
629 .pubgrub
630 .partial_solution
631 .unchanging_term_for_package(next_id),
632 &state.python_requirement,
633 &self.selector,
634 &state.env,
635 )?;
636 }
637
638 version
639 };
640
641 state.prefetcher.version_tried(next_package, &version);
642
643 self.on_progress(next_package, &version);
644
645 if state
646 .added_dependencies
647 .get(&next_id)
648 .is_some_and(|versions| versions.contains(&version))
649 {
650 state
653 .pubgrub
654 .partial_solution
655 .add_decision(next_id, version);
656 continue;
657 }
658
659 let forked_deps = self.get_dependencies_forking(
661 next_id,
662 next_package,
663 &version,
664 &state.pins,
665 &state.fork_urls,
666 &state.env,
667 &state.python_requirement,
668 &state.pubgrub,
669 )?;
670
671 match forked_deps {
672 ForkedDependencies::Unavailable(reason) => {
673 let versions = state.widen_version_to_gap(
676 &version,
677 &self.index,
678 &self.installed_packages,
679 );
680 state
681 .pubgrub
682 .add_incompatibility(Incompatibility::custom_term(
683 next_id,
684 Term::Positive(versions),
685 UnavailableReason::Version(reason),
686 ));
687 }
688 ForkedDependencies::Unforked(dependencies) => {
689 state
690 .added_dependencies
691 .entry(next_id)
692 .or_default()
693 .insert(version.clone());
694
695 state
697 .visit_package_version_dependencies(
698 next_id,
699 &version,
700 &self.urls,
701 &self.indexes,
702 &dependencies,
703 &self.git,
704 &self.workspace_members,
705 self.selector.resolution_strategy(),
706 )
707 .map_err(|err| {
708 enrich_dependency_error(err, next_id, &version, &state.pubgrub)
709 })?;
710
711 self.visit_dependencies(&dependencies, &state, request_sink)
713 .map_err(|err| {
714 enrich_dependency_error(err, next_id, &version, &state.pubgrub)
715 })?;
716
717 state.add_package_version_dependencies(
719 next_id,
720 &version,
721 dependencies,
722 &self.index,
723 &self.installed_packages,
724 );
725 }
726 ForkedDependencies::Forked {
727 mut forks,
728 diverging_packages,
729 } => {
730 state
731 .added_dependencies
732 .entry(next_id)
733 .or_default()
734 .insert(version.clone());
735
736 debug!(
737 "Pre-fork {} took {:.3}s",
738 state.env,
739 start.elapsed().as_secs_f32()
740 );
741
742 match (self.options.fork_strategy, self.options.resolution_mode) {
744 (ForkStrategy::Fewest, _) | (_, ResolutionMode::Lowest) => {
745 forks.sort_by(|a, b| {
749 a.cmp_requires_python(b)
750 .reverse()
751 .then_with(|| a.cmp_upper_bounds(b))
752 });
753 }
754 (ForkStrategy::RequiresPython, _) => {
755 forks.sort_by(|a, b| {
759 a.cmp_requires_python(b).then_with(|| a.cmp_upper_bounds(b))
760 });
761 }
762 }
763
764 for new_fork_state in self.forks_to_fork_states(
765 state,
766 &version,
767 forks,
768 request_sink,
769 &diverging_packages,
770 ) {
771 forked_states.push(new_fork_state?);
772 }
773 continue 'FORK;
774 }
775 ForkedDependencies::RequiresPython(requires_python) => {
776 if matches!(self.options.fork_strategy, ForkStrategy::RequiresPython)
777 && state.env.marker_environment().is_none()
778 {
779 let forks = fork_version_by_python_requirement(
780 &requires_python,
781 &state.python_requirement,
782 &state.env,
783 );
784 if !forks.is_empty() {
785 debug!(
786 "Forking Python requirement `{}` on `{}` for {}=={} ({})",
787 state.python_requirement.target(),
788 &requires_python,
789 next_package,
790 version,
791 forks
792 .iter()
793 .map(ToString::to_string)
794 .collect::<Vec<_>>()
795 .join(", ")
796 );
797
798 let forks = forks
801 .into_iter()
802 .map(|env| VersionFork {
803 env,
804 id: next_id,
805 version: None,
806 })
807 .collect();
808 forked_states
809 .extend(self.version_forks_to_fork_states(state, forks));
810 continue 'FORK;
811 }
812 }
813
814 let versions = state.widen_version_to_gap(
815 &version,
816 &self.index,
817 &self.installed_packages,
818 );
819 state
820 .pubgrub
821 .add_incompatibility(Incompatibility::custom_term(
822 next_id,
823 Term::Positive(versions),
824 UnavailableReason::Version(UnavailableVersion::RequiresPython(
825 requires_python,
826 )),
827 ));
828 }
829 }
830 }
831 }
832 if resolutions.len() > 1 {
833 info!(
834 "Solved your requirements for {} environments",
835 resolutions.len()
836 );
837 }
838 if tracing::enabled!(Level::DEBUG) {
839 for resolution in &resolutions {
840 if let Some(env) = resolution.env.end_user_fork_display() {
841 let packages: FxHashSet<_> = resolution
842 .nodes
843 .keys()
844 .map(|package| &package.name)
845 .collect();
846 debug!(
847 "Distinct solution for {env} with {} package(s)",
848 packages.len()
849 );
850 }
851 }
852 }
853 for resolution in &resolutions {
854 resolution.trace_resolution();
855 }
856 ResolverOutput::from_state(
857 &resolutions,
858 self.requirements.clone(),
859 self.constraints.clone(),
860 self.overrides.clone(),
861 &self.preferences,
862 &self.hasher,
863 &self.index,
864 &self.git,
865 self.python_requirement.target().clone(),
866 &self.conflicts,
867 self.selector.resolution_strategy(),
868 self.options.clone(),
869 )
870 }
871
872 fn forks_to_fork_states<'a>(
874 &'a self,
875 current_state: ForkState,
876 version: &'a Version,
877 forks: Vec<Fork>,
878 request_sink: &'a Sender<Request>,
879 diverging_packages: &'a BTreeSet<PackageName>,
880 ) -> impl Iterator<Item = Result<ForkState, ResolveError>> + 'a {
881 debug!(
882 "Splitting resolution on {}=={} over {} into {} resolution{} with separate markers",
883 current_state.pubgrub.package_store[current_state.next],
884 version,
885 diverging_packages
886 .iter()
887 .map(ToString::to_string)
888 .join(", "),
889 forks.len(),
890 if forks.len() == 1 { "" } else { "s" }
891 );
892 assert!(forks.len() >= 2);
893 let package = current_state.next;
899 let mut cur_state = Some(current_state);
900 let forks_len = forks.len();
901 forks
902 .into_iter()
903 .enumerate()
904 .map(move |(i, fork)| {
905 let is_last = i == forks_len - 1;
906 let forked_state = cur_state.take().unwrap();
907 if !is_last {
908 cur_state = Some(forked_state.clone());
909 }
910
911 let env = fork.env.clone();
912 (fork, forked_state.with_env(env))
913 })
914 .map(move |(fork, mut forked_state)| {
915 forked_state
917 .visit_package_version_dependencies(
918 package,
919 version,
920 &self.urls,
921 &self.indexes,
922 &fork.dependencies,
923 &self.git,
924 &self.workspace_members,
925 self.selector.resolution_strategy(),
926 )
927 .map_err(|err| {
928 enrich_dependency_error(err, package, version, &forked_state.pubgrub)
929 })?;
930
931 self.visit_dependencies(&fork.dependencies, &forked_state, request_sink)
933 .map_err(|err| {
934 enrich_dependency_error(err, package, version, &forked_state.pubgrub)
935 })?;
936
937 forked_state.add_package_version_dependencies(
939 package,
940 version,
941 fork.dependencies,
942 &self.index,
943 &self.installed_packages,
944 );
945
946 Ok(forked_state)
947 })
948 }
949
950 #[expect(clippy::unused_self)]
952 fn version_forks_to_fork_states(
953 &self,
954 current_state: ForkState,
955 forks: Vec<VersionFork>,
956 ) -> impl Iterator<Item = ForkState> + '_ {
957 let mut cur_state = Some(current_state);
963 let forks_len = forks.len();
964 forks.into_iter().enumerate().map(move |(i, fork)| {
965 let is_last = i == forks_len - 1;
966 let mut forked_state = cur_state.take().unwrap();
967 if !is_last {
968 cur_state = Some(forked_state.clone());
969 }
970 forked_state.initial_id = Some(fork.id);
971 forked_state.initial_version = fork.version;
972 forked_state.with_env(fork.env)
973 })
974 }
975
976 fn visit_dependencies(
978 &self,
979 dependencies: &[PubGrubDependency],
980 state: &ForkState,
981 request_sink: &Sender<Request>,
982 ) -> Result<(), ResolveError> {
983 for dependency in dependencies {
984 let PubGrubDependency {
985 package,
986 version: _,
987 parent: _,
988 source: _,
989 } = dependency;
990 let url = package.name().and_then(|name| state.fork_urls.get(name));
991 let index = package.name().and_then(|name| state.fork_indexes.get(name));
992 self.visit_package(package, url, index, request_sink)?;
993 }
994 Ok(())
995 }
996
997 fn visit_package(
1000 &self,
1001 package: &PubGrubPackage,
1002 url: Option<&VerbatimParsedUrl>,
1003 index: Option<&IndexMetadata>,
1004 request_sink: &Sender<Request>,
1005 ) -> Result<(), ResolveError> {
1006 if url.is_none() && package.name().is_none_or(|name| self.urls.any_url(name)) {
1008 return Ok(());
1009 }
1010
1011 self.request_package(package, url, index, request_sink)
1012 }
1013
1014 fn request_package(
1015 &self,
1016 package: &PubGrubPackage,
1017 url: Option<&VerbatimParsedUrl>,
1018 index: Option<&IndexMetadata>,
1019 request_sink: &Sender<Request>,
1020 ) -> Result<(), ResolveError> {
1021 let Some(name) = package.name_no_root() else {
1023 return Ok(());
1024 };
1025
1026 if let Some(url) = url {
1027 if !self.hasher.allows_url(&url.verbatim) {
1029 return Err(ResolveError::UnhashedPackage(name.clone()));
1030 }
1031
1032 let dist = Dist::from_url(name.clone(), url.clone())?;
1034 if self.index.distributions().register(dist.distribution_id()) {
1035 request_sink.blocking_send(Request::Dist(dist))?;
1036 }
1037 } else if let Some(index) = index {
1038 if self
1040 .index
1041 .explicit()
1042 .register((name.clone(), index.url().clone()))
1043 {
1044 request_sink.blocking_send(Request::Package(name.clone(), Some(index.clone())))?;
1045 }
1046 } else {
1047 if self.index.implicit().register(name.clone()) {
1049 request_sink.blocking_send(Request::Package(name.clone(), None))?;
1050 }
1051 }
1052 Ok(())
1053 }
1054
1055 fn pre_visit<'data>(
1058 packages: impl Iterator<
1059 Item = (
1060 Id<PubGrubPackage>,
1061 &'data PubGrubPackage,
1062 &'data Range<Version>,
1063 ),
1064 >,
1065 pre_visited: &mut FxHashMap<Id<PubGrubPackage>, Range<Version>>,
1066 urls: &Urls,
1067 indexes: &Indexes,
1068 python_requirement: &PythonRequirement,
1069 request_sink: &Sender<Request>,
1070 ) -> Result<(), ResolveError> {
1071 for (id, package, range) in packages {
1074 let PubGrubPackageInner::Package {
1075 name,
1076 extra: None,
1077 group: None,
1078 marker: MarkerTree::TRUE,
1079 } = &**package
1080 else {
1081 continue;
1082 };
1083 if urls.any_url(name) {
1086 continue;
1087 }
1088 if indexes.contains_key(name) {
1090 continue;
1091 }
1092 if pre_visited.get(&id) == Some(range) {
1095 continue;
1096 }
1097 pre_visited.insert(id, range.clone());
1098 request_sink.blocking_send(Request::Prefetch(
1099 name.clone(),
1100 range.clone(),
1101 python_requirement.clone(),
1102 ))?;
1103 }
1104 Ok(())
1105 }
1106
1107 #[cfg_attr(feature = "tracing-durations-export", instrument(skip_all, fields(%package)))]
1115 fn choose_version(
1116 &self,
1117 package: &PubGrubPackage,
1118 id: Id<PubGrubPackage>,
1119 index: Option<&IndexUrl>,
1120 range: &Range<Version>,
1121 pins: &mut FilePins,
1122 preferences: &Preferences,
1123 fork_urls: &ForkUrls,
1124 env: &ResolverEnvironment,
1125 python_requirement: &PythonRequirement,
1126 pubgrub: &State<UvDependencyProvider>,
1127 visited: &mut FxHashSet<PackageName>,
1128 request_sink: &Sender<Request>,
1129 ) -> Result<Option<ResolverVersion>, ResolveError> {
1130 match &**package {
1131 PubGrubPackageInner::Root(_) => {
1132 Ok(Some(ResolverVersion::Unforked(MIN_VERSION.clone())))
1133 }
1134
1135 PubGrubPackageInner::Python(_) => {
1136 Ok(None)
1139 }
1140
1141 PubGrubPackageInner::System(_) => {
1142 let Some(version) = range.as_singleton() else {
1145 return Ok(None);
1146 };
1147 Ok(Some(ResolverVersion::Unforked(version.clone())))
1148 }
1149
1150 PubGrubPackageInner::Marker { name, .. }
1151 | PubGrubPackageInner::Extra { name, .. }
1152 | PubGrubPackageInner::Group { name, .. }
1153 | PubGrubPackageInner::Package { name, .. } => {
1154 if let Some(url) = package.name().and_then(|name| fork_urls.get(name)) {
1155 self.choose_version_url(id, name, range, url, env, python_requirement, pubgrub)
1156 } else {
1157 self.choose_version_registry(
1158 package,
1159 id,
1160 name,
1161 index,
1162 range,
1163 preferences,
1164 env,
1165 python_requirement,
1166 pubgrub,
1167 pins,
1168 visited,
1169 request_sink,
1170 )
1171 }
1172 }
1173 }
1174 }
1175
1176 fn choose_version_url(
1179 &self,
1180 id: Id<PubGrubPackage>,
1181 name: &PackageName,
1182 range: &Range<Version>,
1183 url: &VerbatimParsedUrl,
1184 env: &ResolverEnvironment,
1185 python_requirement: &PythonRequirement,
1186 pubgrub: &State<UvDependencyProvider>,
1187 ) -> Result<Option<ResolverVersion>, ResolveError> {
1188 debug!(
1189 "Searching for a compatible version of {name} @ {} ({range})",
1190 url.verbatim
1191 );
1192
1193 let dist = Dist::from_url(name.clone(), url.clone())?;
1194 let distribution_id = dist.distribution_id();
1195 let response = self
1196 .index
1197 .distributions()
1198 .wait_blocking(&distribution_id)
1199 .map_err(|_| ResolveError::UnregisteredTask(dist.to_string()))?;
1200
1201 let metadata = match &*response {
1203 MetadataResponse::Found(archive) => &archive.metadata,
1204 MetadataResponse::Unavailable(reason) => {
1205 self.unavailable_packages
1206 .pin()
1207 .insert(name.clone(), reason.into());
1208 return Ok(None);
1209 }
1210 MetadataResponse::Error(dist, err) => {
1213 return Err(ResolveError::Dist(
1214 DistErrorKind::from_requested_dist(dist, &**err),
1215 dist.clone(),
1216 DerivationChain::default(),
1217 err.clone(),
1218 ));
1219 }
1220 };
1221
1222 let version = &metadata.version;
1223
1224 if !range.contains(version) {
1226 return Ok(None);
1227 }
1228
1229 if let Dist::Built(dist) = &dist {
1232 let filename = match &dist {
1233 BuiltDist::Registry(dist) => &dist.best_wheel().filename,
1234 BuiltDist::DirectUrl(dist) => &dist.filename,
1235 BuiltDist::GitPath(dist) => &dist.filename,
1236 BuiltDist::Path(dist) => &dist.filename,
1237 };
1238
1239 if env.marker_environment().is_none() && !self.options.artifact_environments.is_empty()
1242 {
1243 let wheel_marker = implied_markers(filename);
1244 for environment_marker in self.options.artifact_environments.iter().copied() {
1247 if env.included_by_marker(environment_marker)
1249 && !find_environments(id, pubgrub).is_disjoint(environment_marker)
1250 {
1251 if wheel_marker.is_disjoint(environment_marker) {
1253 return Ok(Some(ResolverVersion::Unavailable(
1254 version.clone(),
1255 UnavailableVersion::IncompatibleDist(IncompatibleDist::Wheel(
1256 IncompatibleWheel::MissingPlatform(environment_marker),
1257 )),
1258 )));
1259 }
1260 }
1261 }
1262 }
1263
1264 if !python_requirement.target().matches_wheel_tag(filename) {
1266 return Ok(Some(ResolverVersion::Unavailable(
1267 filename.version.clone(),
1268 UnavailableVersion::IncompatibleDist(IncompatibleDist::Wheel(
1269 IncompatibleWheel::Tag(IncompatibleTag::AbiPythonVersion),
1270 )),
1271 )));
1272 }
1273 }
1274
1275 if let Some(requires_python) = metadata.requires_python.as_ref() {
1277 if !python_requirement.target().is_contained_by(requires_python) {
1278 let kind = if python_requirement.installed() == python_requirement.target() {
1279 PythonRequirementKind::Installed
1280 } else {
1281 PythonRequirementKind::Target
1282 };
1283 return Ok(Some(ResolverVersion::Unavailable(
1284 version.clone(),
1285 UnavailableVersion::IncompatibleDist(IncompatibleDist::Source(
1286 IncompatibleSource::RequiresPython(requires_python.clone(), kind),
1287 )),
1288 )));
1289 }
1290 }
1291
1292 Ok(Some(ResolverVersion::Unforked(version.clone())))
1293 }
1294
1295 fn choose_version_registry(
1298 &self,
1299 package: &PubGrubPackage,
1300 id: Id<PubGrubPackage>,
1301 name: &PackageName,
1302 index: Option<&IndexUrl>,
1303 range: &Range<Version>,
1304 preferences: &Preferences,
1305 env: &ResolverEnvironment,
1306 python_requirement: &PythonRequirement,
1307 pubgrub: &State<UvDependencyProvider>,
1308 pins: &mut FilePins,
1309 visited: &mut FxHashSet<PackageName>,
1310 request_sink: &Sender<Request>,
1311 ) -> Result<Option<ResolverVersion>, ResolveError> {
1312 let versions_response = if let Some(index) = index {
1314 self.index
1315 .explicit()
1316 .wait_blocking(&(name.clone(), index.clone()))
1317 .map_err(|_| ResolveError::UnregisteredTask(name.to_string()))?
1318 } else {
1319 self.index
1320 .implicit()
1321 .wait_blocking(name)
1322 .map_err(|_| ResolveError::UnregisteredTask(name.to_string()))?
1323 };
1324 visited.insert(name.clone());
1325
1326 let version_maps = match *versions_response {
1327 VersionsResponse::Found(ref version_maps) => version_maps.as_slice(),
1328 VersionsResponse::NoIndex => {
1329 self.unavailable_packages
1330 .pin()
1331 .insert(name.clone(), UnavailablePackage::NoIndex);
1332 &[]
1333 }
1334 VersionsResponse::Offline => {
1335 self.unavailable_packages
1336 .pin()
1337 .insert(name.clone(), UnavailablePackage::Offline);
1338 &[]
1339 }
1340 VersionsResponse::NotFound => {
1341 self.unavailable_packages
1342 .pin()
1343 .insert(name.clone(), UnavailablePackage::NotFound);
1344 &[]
1345 }
1346 };
1347
1348 debug!("Searching for a compatible version of {package} ({range})");
1349
1350 let Some(candidate) = self.selector.select(
1352 name,
1353 range,
1354 version_maps,
1355 preferences,
1356 &self.installed_packages,
1357 &self.exclusions,
1358 index,
1359 env,
1360 self.tags.as_ref(),
1361 ) else {
1362 return Ok(None);
1364 };
1365
1366 let dist = match candidate.dist() {
1367 CandidateDist::Compatible(dist) => dist,
1368 CandidateDist::Incompatible {
1369 incompatible_dist: incompatibility,
1370 prioritized_dist: _,
1371 } => {
1372 return Ok(Some(ResolverVersion::Unavailable(
1374 candidate.version().clone(),
1375 UnavailableVersion::IncompatibleDist(incompatibility.clone()),
1378 )));
1379 }
1380 };
1381
1382 if let Some((requires_python, incompatibility)) =
1384 Self::check_requires_python(dist, python_requirement)
1385 {
1386 if matches!(self.options.fork_strategy, ForkStrategy::RequiresPython) {
1387 if env.marker_environment().is_none() {
1388 let forks = fork_version_by_python_requirement(
1389 requires_python,
1390 python_requirement,
1391 env,
1392 );
1393 if !forks.is_empty() {
1394 debug!(
1395 "Forking Python requirement `{}` on `{}` for {}=={} ({})",
1396 python_requirement.target(),
1397 requires_python,
1398 name,
1399 candidate.version(),
1400 forks
1401 .iter()
1402 .map(ToString::to_string)
1403 .collect::<Vec<_>>()
1404 .join(", ")
1405 );
1406 let forks = forks
1407 .into_iter()
1408 .map(|env| VersionFork {
1409 env,
1410 id,
1411 version: None,
1412 })
1413 .collect();
1414 return Ok(Some(ResolverVersion::Forked(forks)));
1415 }
1416 }
1417 }
1418
1419 return Ok(Some(ResolverVersion::Unavailable(
1420 candidate.version().clone(),
1421 UnavailableVersion::IncompatibleDist(incompatibility),
1422 )));
1423 }
1424
1425 if let Some(forked) = self.fork_version_registry(
1427 &candidate,
1428 dist,
1429 version_maps,
1430 package,
1431 id,
1432 name,
1433 index,
1434 range,
1435 preferences,
1436 env,
1437 pubgrub,
1438 pins,
1439 request_sink,
1440 )? {
1441 return Ok(Some(forked));
1442 }
1443
1444 let filename = match dist.for_installation() {
1445 ResolvedDistRef::InstallableRegistrySourceDist { sdist, .. } => sdist
1446 .filename()
1447 .unwrap_or(Cow::Borrowed("unknown filename")),
1448 ResolvedDistRef::InstallableRegistryBuiltDist { wheel, .. } => wheel
1449 .filename()
1450 .unwrap_or(Cow::Borrowed("unknown filename")),
1451 ResolvedDistRef::Installed { .. } => Cow::Borrowed("installed"),
1452 };
1453
1454 debug!(
1455 "Selecting: {}=={} [{}] ({})",
1456 name,
1457 candidate.version(),
1458 candidate.choice_kind(),
1459 filename,
1460 );
1461 self.visit_candidate(&candidate, dist, package, name, pins, request_sink)?;
1462
1463 let version = candidate.version().clone();
1464 Ok(Some(ResolverVersion::Unforked(version)))
1465 }
1466
1467 fn fork_version_registry(
1480 &self,
1481 candidate: &Candidate,
1482 dist: &CompatibleDist,
1483 version_maps: &[VersionMap],
1484 package: &PubGrubPackage,
1485 id: Id<PubGrubPackage>,
1486 name: &PackageName,
1487 index: Option<&IndexUrl>,
1488 range: &Range<Version>,
1489 preferences: &Preferences,
1490 env: &ResolverEnvironment,
1491 pubgrub: &State<UvDependencyProvider>,
1492 pins: &mut FilePins,
1493 request_sink: &Sender<Request>,
1494 ) -> Result<Option<ResolverVersion>, ResolveError> {
1495 if env.marker_environment().is_some() {
1497 return Ok(None);
1498 }
1499
1500 if dist.implied_markers().is_true() {
1503 return Ok(None);
1504 }
1505
1506 for marker in self.options.artifact_environments.iter().copied() {
1509 if env.included_by_marker(marker) {
1511 if dist.implied_markers().is_disjoint(marker)
1513 && !find_environments(id, pubgrub).is_disjoint(marker)
1514 {
1515 let Some((left, right)) = fork_version_by_marker(env, marker) else {
1517 return Ok(Some(ResolverVersion::Unavailable(
1518 candidate.version().clone(),
1519 UnavailableVersion::IncompatibleDist(IncompatibleDist::Wheel(
1520 IncompatibleWheel::MissingPlatform(marker),
1521 )),
1522 )));
1523 };
1524
1525 debug!(
1526 "Forking on required platform `{}` for {}=={} ({})",
1527 marker.try_to_string().unwrap_or_else(|| "true".to_string()),
1528 name,
1529 candidate.version(),
1530 [&left, &right]
1531 .iter()
1532 .map(ToString::to_string)
1533 .collect::<Vec<_>>()
1534 .join(", ")
1535 );
1536 let forks = vec![
1537 VersionFork {
1538 env: left,
1539 id,
1540 version: None,
1541 },
1542 VersionFork {
1543 env: right,
1544 id,
1545 version: None,
1546 },
1547 ];
1548 return Ok(Some(ResolverVersion::Forked(forks)));
1549 }
1550 }
1551 }
1552
1553 if !candidate.version().is_local() {
1555 return Ok(None);
1556 }
1557
1558 debug!(
1559 "Looking at local version: {}=={}",
1560 name,
1561 candidate.version()
1562 );
1563
1564 let range = range.clone().intersection(&Range::singleton(
1566 candidate.version().clone().without_local(),
1567 ));
1568
1569 let Some(base_candidate) = self.selector.select(
1570 name,
1571 &range,
1572 version_maps,
1573 preferences,
1574 &self.installed_packages,
1575 &self.exclusions,
1576 index,
1577 env,
1578 self.tags.as_ref(),
1579 ) else {
1580 return Ok(None);
1581 };
1582 let CandidateDist::Compatible(base_dist) = base_candidate.dist() else {
1583 return Ok(None);
1584 };
1585
1586 let mut remainder = {
1588 let mut remainder = base_dist.implied_markers();
1589 remainder = remainder.and(dist.implied_markers().negate());
1590 remainder
1591 };
1592 if remainder.is_false() {
1593 return Ok(None);
1594 }
1595
1596 if !env.included_by_marker(remainder) {
1600 return Ok(None);
1601 }
1602
1603 if !env.included_by_marker(dist.implied_markers()) {
1606 let filename = match dist.for_installation() {
1607 ResolvedDistRef::InstallableRegistrySourceDist { sdist, .. } => sdist
1608 .filename()
1609 .unwrap_or(Cow::Borrowed("unknown filename")),
1610 ResolvedDistRef::InstallableRegistryBuiltDist { wheel, .. } => wheel
1611 .filename()
1612 .unwrap_or(Cow::Borrowed("unknown filename")),
1613 ResolvedDistRef::Installed { .. } => Cow::Borrowed("installed"),
1614 };
1615
1616 debug!(
1617 "Preferring non-local candidate: {}=={} [{}] ({})",
1618 name,
1619 base_candidate.version(),
1620 base_candidate.choice_kind(),
1621 filename,
1622 );
1623 self.visit_candidate(
1624 &base_candidate,
1625 base_dist,
1626 package,
1627 name,
1628 pins,
1629 request_sink,
1630 )?;
1631
1632 return Ok(Some(ResolverVersion::Unforked(
1633 base_candidate.version().clone(),
1634 )));
1635 }
1636
1637 for value in [
1646 arcstr::literal!("darwin"),
1647 arcstr::literal!("linux"),
1648 arcstr::literal!("win32"),
1649 ] {
1650 let sys_platform = MarkerTree::expression(MarkerExpression::String {
1651 key: MarkerValueString::SysPlatform,
1652 operator: MarkerOperator::Equal,
1653 value,
1654 });
1655 if dist.implied_markers().is_disjoint(sys_platform)
1656 && !remainder.is_disjoint(sys_platform)
1657 {
1658 remainder = remainder.or(sys_platform);
1659 }
1660 }
1661
1662 let Some((base_env, local_env)) = fork_version_by_marker(env, remainder) else {
1664 return Ok(None);
1665 };
1666
1667 debug!(
1668 "Forking platform for {}=={} ({})",
1669 name,
1670 candidate.version(),
1671 [&base_env, &local_env]
1672 .iter()
1673 .map(ToString::to_string)
1674 .collect::<Vec<_>>()
1675 .join(", ")
1676 );
1677 self.visit_candidate(candidate, dist, package, name, pins, request_sink)?;
1678 self.visit_candidate(
1679 &base_candidate,
1680 base_dist,
1681 package,
1682 name,
1683 pins,
1684 request_sink,
1685 )?;
1686
1687 let forks = vec![
1688 VersionFork {
1689 env: base_env.clone(),
1690 id,
1691 version: Some(base_candidate.version().clone()),
1692 },
1693 VersionFork {
1694 env: local_env.clone(),
1695 id,
1696 version: Some(candidate.version().clone()),
1697 },
1698 ];
1699 Ok(Some(ResolverVersion::Forked(forks)))
1700 }
1701
1702 fn visit_candidate(
1704 &self,
1705 candidate: &Candidate,
1706 dist: &CompatibleDist,
1707 package: &PubGrubPackage,
1708 name: &PackageName,
1709 pins: &mut FilePins,
1710 request_sink: &Sender<Request>,
1711 ) -> Result<(), ResolveError> {
1712 pins.insert(candidate, dist);
1715
1716 if matches!(&**package, PubGrubPackageInner::Package { .. }) {
1718 if self.dependency_mode.is_transitive() {
1719 let dist = dist.for_resolution();
1720 if self.index.distributions().register(dist.distribution_id()) {
1721 if name != dist.name() {
1722 return Err(ResolveError::MismatchedPackageName {
1723 request: "distribution",
1724 expected: name.clone(),
1725 actual: dist.name().clone(),
1726 });
1727 }
1728 if !self
1730 .hasher
1731 .allows_package(candidate.name(), candidate.version())
1732 {
1733 return Err(ResolveError::UnhashedPackage(candidate.name().clone()));
1734 }
1735
1736 let request = Request::from(dist);
1737 request_sink.blocking_send(request)?;
1738 }
1739 }
1740 }
1741
1742 Ok(())
1743 }
1744
1745 fn check_requires_python<'dist>(
1748 dist: &'dist CompatibleDist,
1749 python_requirement: &PythonRequirement,
1750 ) -> Option<(&'dist VersionSpecifiers, IncompatibleDist)> {
1751 let requires_python = dist.requires_python()?;
1752 if python_requirement.target().is_contained_by(requires_python) {
1753 None
1754 } else {
1755 let incompatibility = if matches!(dist, CompatibleDist::CompatibleWheel { .. }) {
1756 IncompatibleDist::Wheel(IncompatibleWheel::RequiresPython(
1757 requires_python.clone(),
1758 if python_requirement.installed() == python_requirement.target() {
1759 PythonRequirementKind::Installed
1760 } else {
1761 PythonRequirementKind::Target
1762 },
1763 ))
1764 } else {
1765 IncompatibleDist::Source(IncompatibleSource::RequiresPython(
1766 requires_python.clone(),
1767 if python_requirement.installed() == python_requirement.target() {
1768 PythonRequirementKind::Installed
1769 } else {
1770 PythonRequirementKind::Target
1771 },
1772 ))
1773 };
1774 Some((requires_python, incompatibility))
1775 }
1776 }
1777
1778 fn get_dependencies_forking(
1780 &self,
1781 id: Id<PubGrubPackage>,
1782 package: &PubGrubPackage,
1783 version: &Version,
1784 pins: &FilePins,
1785 fork_urls: &ForkUrls,
1786 env: &ResolverEnvironment,
1787 python_requirement: &PythonRequirement,
1788 pubgrub: &State<UvDependencyProvider>,
1789 ) -> Result<ForkedDependencies, ResolveError> {
1790 let dependencies = self.get_dependencies(
1791 id,
1792 package,
1793 version,
1794 pins,
1795 fork_urls,
1796 env,
1797 python_requirement,
1798 pubgrub,
1799 )?;
1800 if env.marker_environment().is_some() {
1801 Ok(ForkedDependencies::from_dependencies_platform_specific(
1802 dependencies,
1803 ))
1804 } else {
1805 Ok(ForkedDependencies::from_dependencies_universal(
1806 dependencies,
1807 env,
1808 python_requirement,
1809 &self.conflicts,
1810 ))
1811 }
1812 }
1813
1814 #[instrument(skip_all, fields(%package, %version))]
1816 fn get_dependencies(
1817 &self,
1818 id: Id<PubGrubPackage>,
1819 package: &PubGrubPackage,
1820 version: &Version,
1821 pins: &FilePins,
1822 fork_urls: &ForkUrls,
1823 env: &ResolverEnvironment,
1824 python_requirement: &PythonRequirement,
1825 pubgrub: &State<UvDependencyProvider>,
1826 ) -> Result<Dependencies, ResolveError> {
1827 let dependencies = match &**package {
1828 PubGrubPackageInner::Root(_) => {
1829 let no_dev_deps = BTreeMap::default();
1830 let requirements = self.flatten_requirements(
1831 &self.requirements,
1832 &no_dev_deps,
1833 None,
1834 None,
1835 None,
1836 None,
1837 env,
1838 python_requirement,
1839 );
1840
1841 PubGrubDependency::from_requirements(
1842 &self.conflicts,
1843 requirements,
1844 None,
1845 Some(package),
1846 )
1847 }
1848
1849 PubGrubPackageInner::Package {
1850 name,
1851 extra,
1852 group,
1853 marker: _,
1854 } => {
1855 if self.dependency_mode.is_direct() {
1857 return Ok(Dependencies::Unforkable(Vec::default()));
1858 }
1859
1860 let owned_id;
1862 let distribution_id = if let Some((_, metadata_id)) =
1863 pins.dist_and_id(name, version)
1864 {
1865 metadata_id
1866 } else if let Some(url) = fork_urls.get(name) {
1867 let dist = Dist::from_url(name.clone(), url.clone())?;
1868 owned_id = dist.distribution_id();
1869 &owned_id
1870 } else {
1871 debug_assert!(
1872 false,
1873 "Dependencies were requested for a package without a pinned distribution"
1874 );
1875 return Err(ResolveError::UnregisteredTask(format!("{name}=={version}")));
1876 };
1877
1878 if self.dependency_mode.is_transitive()
1880 && self.unavailable_packages.pin().contains_key(name)
1881 && self.installed_packages.get_packages(name).is_empty()
1882 {
1883 debug_assert!(
1884 false,
1885 "Dependencies were requested for a package that is not available"
1886 );
1887 return Err(ResolveError::PackageUnavailable(name.clone()));
1888 }
1889
1890 let response = self
1892 .index
1893 .distributions()
1894 .wait_blocking(distribution_id)
1895 .map_err(|_| ResolveError::UnregisteredTask(format!("{name}=={version}")))?;
1896
1897 let metadata = match &*response {
1898 MetadataResponse::Found(archive) => &archive.metadata,
1899 MetadataResponse::Unavailable(reason) => {
1900 let unavailable_version = UnavailableVersion::from(reason);
1901 let message = unavailable_version.singular_message();
1902 if let Some(err) = reason.source() {
1903 warn!("{name} {message}: {err}");
1905 } else {
1906 warn!("{name} {message}");
1907 }
1908 let incomplete_packages = self.incomplete_packages.pin();
1909 let versions = incomplete_packages.get_or_insert(
1910 name.clone(),
1911 HashMap::builder().resize_mode(ResizeMode::Blocking).build(),
1912 );
1913 versions.pin().insert(version.clone(), reason.clone());
1914 return Ok(Dependencies::Unavailable(unavailable_version));
1915 }
1916 MetadataResponse::Error(dist, err) => {
1917 let chain = DerivationChainBuilder::from_state(id, version, pubgrub)
1918 .unwrap_or_default();
1919 return Err(ResolveError::Dist(
1920 DistErrorKind::from_requested_dist(dist, &**err),
1921 dist.clone(),
1922 chain,
1923 err.clone(),
1924 ));
1925 }
1926 };
1927
1928 if let Some(requires_python) = &metadata.requires_python {
1931 if !python_requirement.target().is_contained_by(requires_python) {
1932 return Ok(Dependencies::RequiresPython(requires_python.clone()));
1933 }
1934 }
1935
1936 let system_dependencies = self
1938 .options
1939 .torch_backend
1940 .as_ref()
1941 .filter(|torch_backend| matches!(torch_backend, TorchStrategy::Cuda { .. }))
1942 .filter(|torch_backend| torch_backend.has_system_dependency(name))
1943 .and_then(|_| pins.get(name, version).and_then(ResolvedDist::index))
1944 .map(IndexUrl::url)
1945 .and_then(SystemDependency::from_index)
1946 .into_iter()
1947 .inspect(|system_dependency| {
1948 debug!(
1949 "Adding system dependency `{}` for `{package}@{version}`",
1950 system_dependency
1951 );
1952 })
1953 .map(PubGrubDependency::from);
1954
1955 let requirements = self.flatten_requirements(
1956 &metadata.requires_dist,
1957 &metadata.dependency_groups,
1958 extra.as_ref(),
1959 group.as_ref(),
1960 Some(name),
1961 Some(version),
1962 env,
1963 python_requirement,
1964 );
1965
1966 PubGrubDependency::from_requirements(
1967 &self.conflicts,
1968 requirements,
1969 group.as_ref(),
1970 Some(package),
1971 )
1972 .map(|mut dependencies| {
1973 dependencies.extend(system_dependencies);
1974 dependencies
1975 })
1976 }
1977
1978 PubGrubPackageInner::Python(_) => return Ok(Dependencies::Unforkable(Vec::default())),
1979
1980 PubGrubPackageInner::System(_) => return Ok(Dependencies::Unforkable(Vec::default())),
1981
1982 PubGrubPackageInner::Marker { name, marker } => {
1984 return Ok(Dependencies::Unforkable(
1985 [MarkerTree::TRUE, *marker]
1986 .into_iter()
1987 .map(move |marker| PubGrubDependency {
1988 package: PubGrubPackage::from(PubGrubPackageInner::Package {
1989 name: name.clone(),
1990 extra: None,
1991 group: None,
1992 marker,
1993 }),
1994 version: Range::singleton(version.clone()),
1995 parent: None,
1996 source: DependencySource::Unspecified,
1997 })
1998 .collect(),
1999 ));
2000 }
2001
2002 PubGrubPackageInner::Extra {
2004 name,
2005 extra,
2006 marker,
2007 } => {
2008 return Ok(Dependencies::Unforkable(
2009 [MarkerTree::TRUE, *marker]
2010 .into_iter()
2011 .dedup()
2012 .flat_map(move |marker| {
2013 [None, Some(extra)]
2014 .into_iter()
2015 .map(move |extra| PubGrubDependency {
2016 package: PubGrubPackage::from(PubGrubPackageInner::Package {
2017 name: name.clone(),
2018 extra: extra.cloned(),
2019 group: None,
2020 marker,
2021 }),
2022 version: Range::singleton(version.clone()),
2023 parent: None,
2024 source: DependencySource::Unspecified,
2025 })
2026 })
2027 .collect(),
2028 ));
2029 }
2030
2031 PubGrubPackageInner::Group {
2033 name,
2034 group,
2035 marker,
2036 } => {
2037 return Ok(Dependencies::Unforkable(
2038 [MarkerTree::TRUE, *marker]
2039 .into_iter()
2040 .dedup()
2041 .map(|marker| PubGrubDependency {
2042 package: PubGrubPackage::from(PubGrubPackageInner::Package {
2043 name: name.clone(),
2044 extra: None,
2045 group: Some(group.clone()),
2046 marker,
2047 }),
2048 version: Range::singleton(version.clone()),
2049 parent: None,
2050 source: DependencySource::Unspecified,
2051 })
2052 .collect(),
2053 ));
2054 }
2055 };
2056 Ok(match dependencies {
2057 Ok(dependencies) => Dependencies::Available(dependencies),
2058 Err(requirement) => {
2059 Dependencies::Unavailable(UnavailableVersion::UnsatisfiableDependency(requirement))
2060 }
2061 })
2062 }
2063
2064 fn flatten_requirements<'a>(
2068 &'a self,
2069 dependencies: &'a [Requirement],
2070 dev_dependencies: &'a BTreeMap<GroupName, Box<[Requirement]>>,
2071 extra: Option<&'a ExtraName>,
2072 dev: Option<&'a GroupName>,
2073 name: Option<&'a PackageName>,
2074 version: Option<&'a Version>,
2075 env: &'a ResolverEnvironment,
2076 python_requirement: &'a PythonRequirement,
2077 ) -> impl Iterator<Item = Cow<'a, Requirement>> {
2078 let python_marker = python_requirement.to_marker_tree();
2079
2080 if let Some(dev) = dev {
2081 Either::Left(Either::Left(self.requirements_for_extra(
2084 dev_dependencies.get(dev).into_iter().flatten(),
2085 extra,
2086 None,
2087 name.zip(version),
2088 env,
2089 python_marker,
2090 python_requirement,
2091 )))
2092 } else if !dependencies
2093 .iter()
2094 .any(|req| name == Some(&req.name) && !req.extras.is_empty())
2095 {
2096 Either::Left(Either::Right(self.requirements_for_extra(
2098 dependencies.iter(),
2099 extra,
2100 name.zip(version),
2101 name.zip(version),
2102 env,
2103 python_marker,
2104 python_requirement,
2105 )))
2106 } else {
2107 let mut requirements = self
2108 .requirements_for_extra(
2109 dependencies.iter(),
2110 extra,
2111 name.zip(version),
2112 name.zip(version),
2113 env,
2114 python_marker,
2115 python_requirement,
2116 )
2117 .collect::<Vec<_>>();
2118
2119 let mut seen = FxHashSet::<(ExtraName, MarkerTree)>::default();
2122 let mut queue: VecDeque<_> = requirements
2123 .iter()
2124 .filter(|req| name == Some(&req.name))
2125 .flat_map(|req| req.extras.iter().cloned().map(|extra| (extra, req.marker)))
2126 .collect();
2127 while let Some((extra, marker)) = queue.pop_front() {
2128 if !seen.insert((extra.clone(), marker)) {
2129 continue;
2130 }
2131 for requirement in self.requirements_for_extra(
2132 dependencies,
2133 Some(&extra),
2134 name.zip(version),
2135 name.zip(version),
2136 env,
2137 python_marker,
2138 python_requirement,
2139 ) {
2140 let requirement = match requirement {
2141 Cow::Owned(mut requirement) => {
2142 requirement.marker = requirement.marker.and(marker);
2143 requirement
2144 }
2145 Cow::Borrowed(requirement) => {
2146 let mut marker = marker;
2147 marker = marker.and(requirement.marker);
2148 Requirement {
2149 name: requirement.name.clone(),
2150 extras: requirement.extras.clone(),
2151 groups: requirement.groups.clone(),
2152 source: requirement.source.clone(),
2153 origin: requirement.origin.clone(),
2154 marker: marker.simplify_extras(slice::from_ref(&extra)),
2155 }
2156 }
2157 };
2158 let applicable_marker = requirement
2161 .marker
2162 .simplify_extras(slice::from_ref(&extra))
2163 .simplify_not_extras_with(|candidate| candidate != &extra);
2164 if python_marker.is_disjoint(applicable_marker)
2165 || !env.included_by_marker(applicable_marker)
2166 {
2167 continue;
2168 }
2169 if name == Some(&requirement.name) {
2170 queue.extend(
2172 requirement
2173 .extras
2174 .iter()
2175 .cloned()
2176 .map(|extra| (extra, requirement.marker)),
2177 );
2178 }
2179
2180 requirements.push(Cow::Owned(requirement));
2182 }
2183 }
2184
2185 let mut self_constraints = vec![];
2189 for req in &requirements {
2190 if name == Some(&req.name) && !req.extras.is_empty() && !req.source.is_empty() {
2191 self_constraints.push(Requirement {
2192 name: req.name.clone(),
2193 extras: Box::new([]),
2194 groups: req.groups.clone(),
2195 source: req.source.clone(),
2196 origin: req.origin.clone(),
2197 marker: req.marker,
2198 });
2199 }
2200 }
2201
2202 requirements.retain(|req| name != Some(&req.name) || req.extras.is_empty());
2204 requirements.extend(self_constraints.into_iter().map(Cow::Owned));
2205
2206 Either::Right(requirements.into_iter())
2207 }
2208 }
2209
2210 fn requirements_for_extra<'data, 'parameters>(
2213 &'data self,
2214 dependencies: impl IntoIterator<Item = &'data Requirement> + 'parameters,
2215 extra: Option<&'parameters ExtraName>,
2216 override_package: Option<(&'parameters PackageName, &'parameters Version)>,
2217 exclusion_package: Option<(&'parameters PackageName, &'parameters Version)>,
2218 env: &'parameters ResolverEnvironment,
2219 python_marker: MarkerTree,
2220 python_requirement: &'parameters PythonRequirement,
2221 ) -> impl Iterator<Item = Cow<'data, Requirement>> + 'parameters
2222 where
2223 'data: 'parameters,
2224 {
2225 self.overrides
2226 .apply_for_package(override_package, dependencies)
2227 .filter(move |requirement| {
2228 !self
2229 .excludes
2230 .contains_for_package(exclusion_package, &requirement.name)
2231 })
2232 .map(move |mut requirement| {
2233 let marker = match extra {
2242 Some(extra) => requirement
2243 .marker
2244 .simplify_extras(slice::from_ref(extra))
2245 .simplify_not_extras_with(|candidate| candidate != extra)
2246 .and(
2247 requirement
2248 .marker
2249 .simplify_not_extras_with(|_| true)
2250 .negate(),
2251 ),
2252 None => requirement.marker.simplify_not_extras_with(|_| true),
2253 };
2254
2255 if requirement.marker != marker {
2256 requirement.to_mut().marker = marker;
2257 }
2258
2259 requirement
2260 })
2261 .filter(move |requirement| {
2262 Self::is_requirement_applicable(
2263 requirement,
2264 extra,
2265 env,
2266 python_marker,
2267 python_requirement,
2268 )
2269 })
2270 .flat_map(move |requirement| {
2271 iter::once(requirement.clone()).chain(self.constraints_for_requirement(
2272 requirement,
2273 extra,
2274 env,
2275 python_marker,
2276 python_requirement,
2277 ))
2278 })
2279 }
2280
2281 fn is_requirement_applicable(
2284 requirement: &Requirement,
2285 extra: Option<&ExtraName>,
2286 env: &ResolverEnvironment,
2287 python_marker: MarkerTree,
2288 python_requirement: &PythonRequirement,
2289 ) -> bool {
2290 match extra {
2292 Some(source_extra) => {
2293 if !requirement.evaluate_markers(env.marker_environment(), &[]) {
2294 return false;
2295 }
2296
2297 if !env.included_by_group(ConflictItemRef::from((&requirement.name, source_extra)))
2298 {
2299 return false;
2300 }
2301 }
2302 None => {
2303 if !requirement.evaluate_markers(env.marker_environment(), &[]) {
2304 return false;
2305 }
2306 }
2307 }
2308
2309 if python_marker.is_disjoint(requirement.marker) {
2312 trace!(
2313 "Skipping {requirement} because of Requires-Python: {requires_python}",
2314 requires_python = python_requirement.target(),
2315 );
2316 return false;
2317 }
2318
2319 if !env.included_by_marker(requirement.marker) {
2322 trace!("Skipping {requirement} because of {env}");
2323 return false;
2324 }
2325
2326 true
2327 }
2328
2329 fn constraints_for_requirement<'data, 'parameters>(
2332 &'data self,
2333 requirement: Cow<'data, Requirement>,
2334 extra: Option<&'parameters ExtraName>,
2335 env: &'parameters ResolverEnvironment,
2336 python_marker: MarkerTree,
2337 python_requirement: &'parameters PythonRequirement,
2338 ) -> impl Iterator<Item = Cow<'data, Requirement>> + 'parameters
2339 where
2340 'data: 'parameters,
2341 {
2342 self.constraints
2343 .get(&requirement.name)
2344 .into_iter()
2345 .flatten()
2346 .filter_map(move |constraint| {
2347 let constraint = if constraint.marker.is_true() {
2350 if requirement.marker.is_true() {
2354 Cow::Borrowed(constraint)
2355 } else {
2356 let mut marker = constraint.marker;
2357 marker = marker.and(requirement.marker);
2358
2359 if marker.is_false() {
2360 trace!(
2361 "Skipping {constraint} because of disjoint markers: `{}` vs. `{}`",
2362 constraint.marker.try_to_string().unwrap(),
2363 requirement.marker.try_to_string().unwrap(),
2364 );
2365 return None;
2366 }
2367
2368 Cow::Owned(Requirement {
2369 name: constraint.name.clone(),
2370 extras: constraint.extras.clone(),
2371 groups: constraint.groups.clone(),
2372 source: constraint.source.clone(),
2373 origin: constraint.origin.clone(),
2374 marker,
2375 })
2376 }
2377 } else {
2378 let requires_python = python_requirement.target();
2379
2380 let mut marker = constraint.marker;
2381 marker = 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 if python_marker.is_disjoint(marker) {
2396 trace!(
2397 "Skipping constraint {requirement} because of Requires-Python: {requires_python}"
2398 );
2399 return None;
2400 }
2401
2402 if marker == constraint.marker {
2403 Cow::Borrowed(constraint)
2404 } else {
2405 Cow::Owned(Requirement {
2406 name: constraint.name.clone(),
2407 extras: constraint.extras.clone(),
2408 groups: constraint.groups.clone(),
2409 source: constraint.source.clone(),
2410 origin: constraint.origin.clone(),
2411 marker,
2412 })
2413 }
2414 };
2415
2416 if !env.included_by_marker(constraint.marker) {
2419 trace!("Skipping {constraint} because of {env}");
2420 return None;
2421 }
2422
2423 match extra {
2425 Some(source_extra) => {
2426 if !constraint
2427 .evaluate_markers(env.marker_environment(), slice::from_ref(source_extra))
2428 {
2429 return None;
2430 }
2431 if !env.included_by_group(ConflictItemRef::from((&requirement.name, source_extra)))
2432 {
2433 return None;
2434 }
2435 }
2436 None => {
2437 if !constraint.evaluate_markers(env.marker_environment(), &[]) {
2438 return None;
2439 }
2440 }
2441 }
2442
2443 Some(constraint)
2444 })
2445 }
2446
2447 async fn fetch<Provider: ResolverProvider>(
2449 self: Arc<Self>,
2450 provider: Arc<Provider>,
2451 request_stream: Receiver<Request>,
2452 ) -> Result<(), ResolveError> {
2453 let mut response_stream = ReceiverStream::new(request_stream)
2454 .map(|request| self.process_request(request, &*provider).boxed_local())
2455 .buffer_unordered(usize::MAX);
2459
2460 while let Some(response) = response_stream.next().await {
2461 match response? {
2462 Some(Response::Package(name, index, version_map)) => {
2463 trace!("Received package metadata for: {name}");
2464 if let Some(index) = index {
2465 self.index
2466 .explicit()
2467 .done((name, index), Arc::new(version_map));
2468 } else {
2469 self.index.implicit().done(name, Arc::new(version_map));
2470 }
2471 }
2472 Some(Response::Installed { dist, metadata }) => {
2473 trace!("Received installed distribution metadata for: {dist}");
2474 self.index
2475 .distributions()
2476 .done(dist.distribution_id(), Arc::new(metadata));
2477 }
2478 Some(Response::Dist { dist, metadata }) => {
2479 let dist_kind = match dist {
2480 Dist::Built(_) => "built",
2481 Dist::Source(_) => "source",
2482 };
2483 trace!("Received {dist_kind} distribution metadata for: {dist}");
2484 if let MetadataResponse::Unavailable(reason) = &metadata {
2485 let message = UnavailableVersion::from(reason).singular_message();
2486 if let Some(err) = reason.source() {
2487 warn!("{dist} {message}: {err}");
2489 } else {
2490 warn!("{dist} {message}");
2491 }
2492 }
2493 self.index
2494 .distributions()
2495 .done(dist.distribution_id(), Arc::new(metadata));
2496 }
2497 None => {}
2498 }
2499 }
2500
2501 Ok::<(), ResolveError>(())
2502 }
2503
2504 #[instrument(skip_all, fields(%request))]
2505 async fn process_request<Provider: ResolverProvider>(
2506 &self,
2507 request: Request,
2508 provider: &Provider,
2509 ) -> Result<Option<Response>, ResolveError> {
2510 match request {
2511 Request::Package(package_name, index) => {
2513 let package_versions = provider
2514 .get_package_versions(&package_name, index.as_ref())
2515 .boxed_local()
2516 .await
2517 .map_err(ResolveError::Client)?;
2518
2519 Ok(Some(Response::Package(
2520 package_name,
2521 index.map(IndexMetadata::into_url),
2522 package_versions,
2523 )))
2524 }
2525
2526 Request::Dist(dist) => {
2528 if let Some(version) = dist.version() {
2529 if let Some(index) = dist.index() {
2530 let versions_response = self.index.implicit().get(dist.name());
2532 if let Some(VersionsResponse::Found(version_maps)) =
2533 versions_response.as_deref()
2534 {
2535 for version_map in version_maps {
2536 if version_map.index() == Some(index) {
2537 let Some(metadata) = version_map.get_metadata(version) else {
2538 continue;
2539 };
2540 debug!("Found registry-provided metadata for: {dist}");
2541 return Ok(Some(Response::Dist {
2542 dist,
2543 metadata: MetadataResponse::Found(
2544 ArchiveMetadata::from_metadata23(metadata),
2545 ),
2546 }));
2547 }
2548 }
2549 }
2550
2551 let versions_response = self
2553 .index
2554 .explicit()
2555 .get(&(dist.name().clone(), index.clone()));
2556 if let Some(VersionsResponse::Found(version_maps)) =
2557 versions_response.as_deref()
2558 {
2559 for version_map in version_maps {
2560 let Some(metadata) = version_map.get_metadata(version) else {
2561 continue;
2562 };
2563 debug!("Found registry-provided metadata for: {dist}");
2564 return Ok(Some(Response::Dist {
2565 dist,
2566 metadata: MetadataResponse::Found(
2567 ArchiveMetadata::from_metadata23(metadata),
2568 ),
2569 }));
2570 }
2571 }
2572 }
2573 }
2574
2575 let metadata = provider
2576 .get_or_build_wheel_metadata(&dist)
2577 .boxed_local()
2578 .await?;
2579
2580 if let MetadataResponse::Found(metadata) = &metadata {
2581 if &metadata.metadata.name != dist.name() {
2582 return Err(ResolveError::MismatchedPackageName {
2583 request: "distribution metadata",
2584 expected: dist.name().clone(),
2585 actual: metadata.metadata.name.clone(),
2586 });
2587 }
2588 }
2589
2590 Ok(Some(Response::Dist { dist, metadata }))
2591 }
2592
2593 Request::Installed(dist) => {
2594 let metadata = provider.get_installed_metadata(&dist).boxed_local().await?;
2595
2596 if let MetadataResponse::Found(metadata) = &metadata {
2597 if &metadata.metadata.name != dist.name() {
2598 return Err(ResolveError::MismatchedPackageName {
2599 request: "installed metadata",
2600 expected: dist.name().clone(),
2601 actual: metadata.metadata.name.clone(),
2602 });
2603 }
2604 }
2605
2606 Ok(Some(Response::Installed { dist, metadata }))
2607 }
2608
2609 Request::Prefetch(package_name, range, python_requirement) => {
2611 let versions_response = self
2613 .index
2614 .implicit()
2615 .wait(&package_name)
2616 .await
2617 .map_err(|_| ResolveError::UnregisteredTask(package_name.to_string()))?;
2618
2619 let version_map = match *versions_response {
2620 VersionsResponse::Found(ref version_map) => version_map,
2621 VersionsResponse::NoIndex => {
2623 self.unavailable_packages
2624 .pin()
2625 .insert(package_name.clone(), UnavailablePackage::NoIndex);
2626
2627 return Ok(None);
2628 }
2629 VersionsResponse::Offline => {
2630 self.unavailable_packages
2631 .pin()
2632 .insert(package_name.clone(), UnavailablePackage::Offline);
2633
2634 return Ok(None);
2635 }
2636 VersionsResponse::NotFound => {
2637 self.unavailable_packages
2638 .pin()
2639 .insert(package_name.clone(), UnavailablePackage::NotFound);
2640
2641 return Ok(None);
2642 }
2643 };
2644
2645 let env = ResolverEnvironment::universal(vec![]);
2647
2648 let Some(candidate) = self.selector.select(
2651 &package_name,
2652 &range,
2653 version_map,
2654 &self.preferences,
2655 &self.installed_packages,
2656 &self.exclusions,
2657 None,
2658 &env,
2659 self.tags.as_ref(),
2660 ) else {
2661 return Ok(None);
2662 };
2663
2664 let Some(dist) = candidate.compatible() else {
2666 return Ok(None);
2667 };
2668
2669 for version_map in version_map {
2671 if let Some(metadata) = version_map.get_metadata(candidate.version()) {
2672 let dist = dist.for_resolution();
2673 if version_map.index() == dist.index() {
2674 debug!("Found registry-provided metadata for: {dist}");
2675
2676 let metadata =
2677 MetadataResponse::Found(ArchiveMetadata::from_metadata23(metadata));
2678
2679 let dist = dist.to_owned();
2680 if &package_name != dist.name() {
2681 return Err(ResolveError::MismatchedPackageName {
2682 request: "distribution",
2683 expected: package_name,
2684 actual: dist.name().clone(),
2685 });
2686 }
2687
2688 let response = match dist {
2689 ResolvedDist::Installable { dist, .. } => Response::Dist {
2690 dist: (*dist).clone(),
2691 metadata,
2692 },
2693 ResolvedDist::Installed { dist } => Response::Installed {
2694 dist: (*dist).clone(),
2695 metadata,
2696 },
2697 };
2698
2699 return Ok(Some(response));
2700 }
2701 }
2702 }
2703
2704 if dist.wheel().is_none() {
2708 if !self.selector.use_highest_version(&package_name, &env) {
2709 if let Some((lower, _)) = range.iter().next() {
2710 if lower == Bound::Unbounded {
2711 debug!(
2712 "Skipping prefetch for unbounded minimum-version range: {package_name} ({range})"
2713 );
2714 return Ok(None);
2715 }
2716 }
2717 }
2718 }
2719
2720 if let Some(requires_python) = dist.requires_python() {
2722 if !python_requirement.target().is_contained_by(requires_python) {
2723 return Ok(None);
2724 }
2725 }
2726
2727 if !self
2729 .hasher
2730 .allows_package(candidate.name(), candidate.version())
2731 {
2732 return Ok(None);
2733 }
2734
2735 let dist = dist.for_resolution();
2737 if self.index.distributions().register(dist.distribution_id()) {
2738 let dist = dist.to_owned();
2739 if &package_name != dist.name() {
2740 return Err(ResolveError::MismatchedPackageName {
2741 request: "distribution",
2742 expected: package_name,
2743 actual: dist.name().clone(),
2744 });
2745 }
2746
2747 let response = match dist {
2748 ResolvedDist::Installable { dist, .. } => {
2749 let metadata = provider
2750 .get_or_build_wheel_metadata(&dist)
2751 .boxed_local()
2752 .await?;
2753
2754 Response::Dist {
2755 dist: (*dist).clone(),
2756 metadata,
2757 }
2758 }
2759 ResolvedDist::Installed { dist } => {
2760 let metadata =
2761 provider.get_installed_metadata(&dist).boxed_local().await?;
2762
2763 Response::Installed {
2764 dist: (*dist).clone(),
2765 metadata,
2766 }
2767 }
2768 };
2769
2770 Ok(Some(response))
2771 } else {
2772 Ok(None)
2773 }
2774 }
2775 }
2776 }
2777
2778 fn convert_no_solution_err(
2779 &self,
2780 mut err: pubgrub::NoSolutionError<UvDependencyProvider>,
2781 fork_urls: ForkUrls,
2782 fork_indexes: ForkIndexes,
2783 known_versions: &FxHashMap<PackageName, Arc<[Version]>>,
2784 env: ResolverEnvironment,
2785 current_environment: MarkerEnvironment,
2786 visited: &FxHashSet<PackageName>,
2787 ) -> ResolveError {
2788 err = NoSolutionError::collapse_local_version_segments(NoSolutionError::collapse_proxies(
2789 err,
2790 ));
2791 err = NoSolutionError::narrow_widened_sets(err, known_versions);
2792
2793 let mut unavailable_packages = FxHashMap::default();
2794 for package in derivation_tree_packages(&err) {
2795 if let PubGrubPackageInner::Package { name, .. } = &**package {
2796 if let Some(reason) = self.unavailable_packages.pin().get(name) {
2797 unavailable_packages.insert(name.clone(), reason.clone());
2798 }
2799 }
2800 }
2801
2802 let mut incomplete_packages = FxHashMap::default();
2803 let incomplete_packages_cache = self.incomplete_packages.pin();
2804 for package in derivation_tree_packages(&err) {
2805 if let PubGrubPackageInner::Package { name, .. } = &**package
2806 && let Some(versions) = incomplete_packages_cache.get(name)
2807 {
2808 for (version, reason) in &versions.pin() {
2809 incomplete_packages
2810 .entry(name.clone())
2811 .or_insert_with(BTreeMap::default)
2812 .insert(version.clone(), reason.clone());
2813 }
2814 }
2815 }
2816
2817 let mut available_indexes = FxHashMap::default();
2818 let mut included_versions = FxHashMap::default();
2819 let mut available_versions = FxHashMap::default();
2820
2821 let available_version_cutoff: Option<jiff::Timestamp> =
2822 std::env::var(EnvVars::UV_TEST_AVAILABLE_VERSION_CUTOFF)
2823 .ok()
2824 .and_then(|s| s.parse().ok());
2825
2826 for package in derivation_tree_packages(&err) {
2827 let Some(name) = package.name() else { continue };
2828 if !visited.contains(name) {
2829 continue;
2834 }
2835 let versions_response = if let Some(index) = fork_indexes.get(name) {
2836 self.index
2837 .explicit()
2838 .get(&(name.clone(), index.url().clone()))
2839 } else {
2840 self.index.implicit().get(name)
2841 };
2842 if let Some(response) = versions_response {
2843 if let VersionsResponse::Found(ref version_maps) = *response {
2844 for version_map in version_maps {
2846 let package_included_versions = included_versions
2847 .entry(name.clone())
2848 .or_insert_with(BTreeSet::new);
2849 let package_available_versions = available_versions
2850 .entry(name.clone())
2851 .or_insert_with(BTreeSet::new);
2852
2853 for (version, dists) in version_map.iter(&Ranges::full()) {
2854 let excluded_from_included = || {
2859 let Some(included_version_cutoff) =
2860 version_map.included_version_cutoff()
2861 else {
2862 return false;
2863 };
2864 let Some(prioritized_dist) = dists.prioritized_dist() else {
2865 return true;
2866 };
2867 prioritized_dist.files().all(|file| {
2868 file.upload_time_utc_ms.is_none_or(|upload_time| {
2869 upload_time >= included_version_cutoff.as_millisecond()
2870 })
2871 })
2872 };
2873
2874 if !excluded_from_included() {
2875 package_included_versions.insert(version.clone());
2876 }
2877
2878 let excluded_from_available = || {
2884 let Some(ref exclude_newer) = available_version_cutoff else {
2885 return false;
2886 };
2887 let Some(prioritized_dist) = dists.prioritized_dist() else {
2888 return false;
2889 };
2890 prioritized_dist.files().all(|file| {
2891 file.upload_time_utc_ms.is_some_and(|upload_time| {
2892 upload_time >= exclude_newer.as_millisecond()
2893 })
2894 })
2895 };
2896
2897 if !excluded_from_available() {
2898 package_available_versions.insert(version.clone());
2899 }
2900 }
2901 }
2902
2903 available_indexes
2905 .entry(name.clone())
2906 .or_insert(BTreeSet::new())
2907 .extend(
2908 version_maps
2909 .iter()
2910 .filter_map(|version_map| version_map.index().cloned()),
2911 );
2912 }
2913 }
2914 }
2915
2916 ResolveError::NoSolution(Box::new(NoSolutionError::new(
2917 err,
2918 self.index.clone(),
2919 included_versions,
2920 available_versions,
2921 available_indexes,
2922 self.selector.clone(),
2923 self.python_requirement.clone(),
2924 self.locations.clone(),
2925 self.capabilities.clone(),
2926 unavailable_packages,
2927 incomplete_packages,
2928 fork_urls,
2929 fork_indexes,
2930 env,
2931 current_environment,
2932 self.tags.clone(),
2933 self.workspace_members.clone(),
2934 self.options.clone(),
2935 )))
2936 }
2937
2938 fn on_progress(&self, package: &PubGrubPackage, version: &Version) {
2939 if let Some(reporter) = self.reporter.as_ref() {
2940 match &**package {
2941 PubGrubPackageInner::Root(_) => {}
2942 PubGrubPackageInner::Python(_) => {}
2943 PubGrubPackageInner::System(_) => {}
2944 PubGrubPackageInner::Marker { .. } => {}
2945 PubGrubPackageInner::Extra { .. } => {}
2946 PubGrubPackageInner::Group { .. } => {}
2947 PubGrubPackageInner::Package { name, .. } => {
2948 reporter.on_progress(name, &VersionOrUrlRef::Version(version));
2949 }
2950 }
2951 }
2952 }
2953
2954 fn on_complete(&self) {
2955 if let Some(reporter) = self.reporter.as_ref() {
2956 reporter.on_complete();
2957 }
2958 }
2959}
2960
2961#[derive(Clone, Default)]
2964struct KnownVersions(FxHashMap<PackageName, Arc<[Version]>>);
2965
2966impl KnownVersions {
2967 fn get_or_update<'a, InstalledPackages: InstalledPackagesProvider>(
2980 &'a mut self,
2981 index: &InMemoryIndex,
2982 installed_packages: &InstalledPackages,
2983 fork_urls: &ForkUrls,
2984 fork_indexes: &ForkIndexes,
2985 package: &PubGrubPackage,
2986 ) -> Option<&'a [Version]> {
2987 let name = package.name_no_root()?;
2988 if fork_urls.get(name).is_some() {
2990 return None;
2991 }
2992 if !self.0.contains_key(name) {
2993 let response = if let Some(index_metadata) = fork_indexes.get(name) {
2994 index
2995 .explicit()
2996 .get(&(name.clone(), index_metadata.url().clone()))?
2997 } else {
2998 index.implicit().get(name)?
2999 };
3000 let VersionsResponse::Found(ref version_maps) = *response else {
3001 return None;
3002 };
3003 let mut versions: Vec<Version> = version_maps
3004 .iter()
3005 .flat_map(|version_map| version_map.included_versions().cloned())
3006 .chain(
3007 installed_packages
3008 .get_packages(name)
3009 .iter()
3010 .map(|dist| dist.version().clone()),
3011 )
3012 .collect();
3013 versions.sort_unstable();
3014 versions.dedup();
3015 self.0.insert(name.clone(), versions.into());
3016 }
3017 Some(&self.0[name][..])
3018 }
3019}
3020
3021#[derive(Clone)]
3023pub(crate) struct ForkState {
3024 pubgrub: State<UvDependencyProvider>,
3032 initial_id: Option<Id<PubGrubPackage>>,
3035 initial_version: Option<Version>,
3038 next: Id<PubGrubPackage>,
3040 pins: FilePins,
3049 fork_urls: ForkUrls,
3055 fork_indexes: ForkIndexes,
3060 priorities: PubGrubPriorities,
3066 added_dependencies: FxHashMap<Id<PubGrubPackage>, FxHashSet<Version>>,
3069 pre_visited: FxHashMap<Id<PubGrubPackage>, Range<Version>>,
3071 selected_versions: FxHashMap<Id<PubGrubPackage>, (Range<Version>, Version)>,
3073 known_versions: KnownVersions,
3077 env: ResolverEnvironment,
3092 python_requirement: PythonRequirement,
3105 conflict_tracker: ConflictTracker,
3106 prefetcher: BatchPrefetcher,
3110}
3111
3112impl ForkState {
3113 fn new(
3114 pubgrub: State<UvDependencyProvider>,
3115 env: ResolverEnvironment,
3116 python_requirement: PythonRequirement,
3117 prefetcher: BatchPrefetcher,
3118 ) -> Self {
3119 Self {
3120 initial_id: None,
3121 initial_version: None,
3122 next: pubgrub.root_package,
3123 pubgrub,
3124 pins: FilePins::default(),
3125 fork_urls: ForkUrls::default(),
3126 fork_indexes: ForkIndexes::default(),
3127 priorities: PubGrubPriorities::default(),
3128 added_dependencies: FxHashMap::default(),
3129 pre_visited: FxHashMap::default(),
3130 selected_versions: FxHashMap::default(),
3131 known_versions: KnownVersions::default(),
3132 env,
3133 python_requirement,
3134 conflict_tracker: ConflictTracker::default(),
3135 prefetcher,
3136 }
3137 }
3138
3139 fn visit_package_version_dependencies(
3142 &mut self,
3143 for_package: Id<PubGrubPackage>,
3144 for_version: &Version,
3145 urls: &Urls,
3146 indexes: &Indexes,
3147 dependencies: &[PubGrubDependency],
3148 git: &GitResolver,
3149 workspace_members: &BTreeSet<PackageName>,
3150 resolution_strategy: &ResolutionStrategy,
3151 ) -> Result<(), ResolveError> {
3152 for dependency in dependencies {
3153 let PubGrubDependency {
3154 package,
3155 version,
3156 parent: _,
3157 source,
3158 } = dependency;
3159
3160 let mut has_url = false;
3161 if let Some(name) = package.name() {
3162 for url in urls.get_url(&self.env, name, source.verbatim_url(), git)? {
3167 self.fork_urls.insert(name, url, &self.env)?;
3168 has_url = true;
3169 }
3170
3171 if let Some(index) = source.explicit_index() {
3172 self.fork_indexes.insert(name, index, &self.env)?;
3173 }
3174
3175 for index in indexes.get(name, &self.env) {
3177 self.fork_indexes.insert(name, index, &self.env)?;
3178 }
3179 }
3180
3181 if let Some(name) = self.pubgrub.package_store[for_package]
3182 .name_no_root()
3183 .filter(|name| !workspace_members.contains(name))
3184 {
3185 debug!(
3186 "Adding transitive dependency for {name}=={for_version}: {package}{version}"
3187 );
3188 } else {
3189 debug!("Adding direct dependency: {package}{version}");
3191
3192 let missing_lower_bound = version
3194 .bounding_range()
3195 .is_none_or(|(lowest, _highest)| lowest == Bound::Unbounded);
3196 let strategy_lowest = matches!(
3197 resolution_strategy,
3198 ResolutionStrategy::Lowest | ResolutionStrategy::LowestDirect(..)
3199 );
3200
3201 if !has_url && missing_lower_bound && strategy_lowest {
3202 let name = package.name_no_root().unwrap();
3203 let bound_on_other_package = dependencies.iter().any(|other| {
3210 Some(name) == other.package.name()
3211 && !other
3212 .version
3213 .bounding_range()
3214 .is_none_or(|(lowest, _highest)| lowest == Bound::Unbounded)
3215 });
3216
3217 if !bound_on_other_package {
3218 warn_user_once!(
3219 "The direct dependency `{name}` is unpinned. \
3220 Consider setting a lower bound when using `--resolution lowest` \
3221 or `--resolution lowest-direct` to avoid using outdated versions.",
3222 );
3223 }
3224 }
3225 }
3226
3227 self.priorities.insert(package, version, &self.fork_urls);
3229 if let Some(base_package) = package.base_package() {
3232 self.priorities
3233 .insert(&base_package, version, &self.fork_urls);
3234 }
3235 }
3236
3237 Ok(())
3238 }
3239
3240 fn add_package_version_dependencies<InstalledPackages: InstalledPackagesProvider>(
3246 &mut self,
3247 for_package: Id<PubGrubPackage>,
3248 for_version: &Version,
3249 dependencies: Vec<PubGrubDependency>,
3250 index: &InMemoryIndex,
3251 installed_packages: &InstalledPackages,
3252 ) {
3253 for dependency in &dependencies {
3254 let PubGrubDependency {
3255 package,
3256 version,
3257 parent: _,
3258 source: _,
3259 } = dependency;
3260
3261 let Some(base_package) = package.base_package() else {
3262 continue;
3263 };
3264
3265 let proxy_package = self.pubgrub.package_store.alloc(package.clone());
3266 let base_package_id = self.pubgrub.package_store.alloc(base_package.clone());
3267 self.pubgrub.add_proxy_package_incompatibility(
3268 proxy_package,
3269 base_package_id,
3270 version.clone(),
3271 );
3272 }
3273
3274 let versions = self.widen_version_to_gap(for_version, index, installed_packages);
3277 let conflict = self.pubgrub.add_package_version_dependencies(
3278 self.next,
3279 for_version.clone(),
3280 versions,
3281 dependencies.into_iter().map(|dependency| {
3282 let PubGrubDependency {
3283 package,
3284 version,
3285 parent: _,
3286 source: _,
3287 } = dependency;
3288 (package, version)
3289 }),
3290 );
3291
3292 if let Some(incompatibility) = conflict {
3295 self.record_conflict(for_package, Some(for_version), incompatibility);
3296 }
3297 }
3298
3299 fn widen_version_to_gap<InstalledPackages: InstalledPackagesProvider>(
3301 &mut self,
3302 version: &Version,
3303 index: &InMemoryIndex,
3304 installed_packages: &InstalledPackages,
3305 ) -> Range<Version> {
3306 widen_to_gap(
3307 version,
3308 self.known_versions.get_or_update(
3309 index,
3310 installed_packages,
3311 &self.fork_urls,
3312 &self.fork_indexes,
3313 &self.pubgrub.package_store[self.next],
3314 ),
3315 )
3316 }
3317
3318 fn record_conflict(
3319 &mut self,
3320 affected: Id<PubGrubPackage>,
3321 version: Option<&Version>,
3322 incompatibility: IncompId<PubGrubPackage, Range<Version>, UnavailableReason>,
3323 ) {
3324 let mut culprit_is_real = false;
3325 for (incompatible, _term) in self.pubgrub.incompatibility_store[incompatibility].iter() {
3326 if incompatible == affected {
3327 continue;
3328 }
3329 if self.pubgrub.package_store[affected].name()
3330 == self.pubgrub.package_store[incompatible].name()
3331 {
3332 continue;
3335 }
3336 culprit_is_real = true;
3337 let culprit_count = self
3338 .conflict_tracker
3339 .culprit
3340 .entry(incompatible)
3341 .or_default();
3342 *culprit_count += 1;
3343 if *culprit_count == CONFLICT_THRESHOLD {
3344 self.conflict_tracker.deprioritize.push(incompatible);
3345 }
3346 }
3347 if culprit_is_real {
3350 if tracing::enabled!(Level::DEBUG) {
3351 let incompatibility = self.pubgrub.incompatibility_store[incompatibility]
3352 .iter()
3353 .map(|(package, _term)| &self.pubgrub.package_store[package])
3354 .join(", ");
3355 if let Some(version) = version {
3356 debug!(
3357 "Recording dependency conflict of {}=={} from incompatibility of ({})",
3358 self.pubgrub.package_store[affected], version, incompatibility
3359 );
3360 } else {
3361 debug!(
3362 "Recording unit propagation conflict of {} from incompatibility of ({})",
3363 self.pubgrub.package_store[affected], incompatibility
3364 );
3365 }
3366 }
3367
3368 let affected_count = self.conflict_tracker.affected.entry(self.next).or_default();
3369 *affected_count += 1;
3370 if *affected_count == CONFLICT_THRESHOLD {
3371 self.conflict_tracker.prioritize.push(self.next);
3372 }
3373 }
3374 }
3375
3376 fn reprioritize_conflicts(&mut self) {
3380 for package in self.conflict_tracker.prioritize.drain(..) {
3381 let changed = self
3382 .priorities
3383 .mark_conflict_early(&self.pubgrub.package_store[package]);
3384 if changed {
3385 debug!(
3386 "Package {} has too many conflicts (affected), prioritizing",
3387 &self.pubgrub.package_store[package]
3388 );
3389 } else {
3390 debug!(
3391 "Package {} has too many conflicts (affected), already {:?}",
3392 self.pubgrub.package_store[package],
3393 self.priorities.get(&self.pubgrub.package_store[package])
3394 );
3395 }
3396 }
3397
3398 for package in self.conflict_tracker.deprioritize.drain(..) {
3399 let changed = self
3400 .priorities
3401 .mark_conflict_late(&self.pubgrub.package_store[package]);
3402 if changed {
3403 debug!(
3404 "Package {} has too many conflicts (culprit), deprioritizing and backtracking",
3405 self.pubgrub.package_store[package],
3406 );
3407 let backtrack_level = self.pubgrub.backtrack_package(package);
3408 if let Some(backtrack_level) = backtrack_level {
3409 debug!("Backtracked {backtrack_level} decisions");
3410 } else {
3411 debug!(
3412 "Package {} is not decided, cannot backtrack",
3413 self.pubgrub.package_store[package]
3414 );
3415 }
3416 } else {
3417 debug!(
3418 "Package {} has too many conflicts (culprit), already {:?}",
3419 self.pubgrub.package_store[package],
3420 self.priorities.get(&self.pubgrub.package_store[package])
3421 );
3422 }
3423 }
3424 }
3425
3426 fn add_unavailable_version<InstalledPackages: InstalledPackagesProvider>(
3434 &mut self,
3435 version: Version,
3436 reason: UnavailableVersion,
3437 index: &InMemoryIndex,
3438 installed_packages: &InstalledPackages,
3439 ) {
3440 let versions = self.widen_version_to_gap(&version, index, installed_packages);
3441
3442 if let UnavailableVersion::IncompatibleDist(
3446 IncompatibleDist::Source(IncompatibleSource::RequiresPython(requires_python, kind))
3447 | IncompatibleDist::Wheel(IncompatibleWheel::RequiresPython(requires_python, kind)),
3448 ) = reason
3449 {
3450 let package = &self.next;
3451 let python = self.pubgrub.package_store.alloc(PubGrubPackage::from(
3452 PubGrubPackageInner::Python(match kind {
3453 PythonRequirementKind::Installed => PubGrubPython::Installed,
3454 PythonRequirementKind::Target => PubGrubPython::Target,
3455 }),
3456 ));
3457 self.pubgrub
3458 .add_incompatibility(Incompatibility::from_dependency(
3459 *package,
3460 versions,
3461 (
3462 python,
3463 Range::from_versions(release_specifiers_to_ranges(requires_python)),
3464 ),
3465 ));
3466 self.pubgrub
3467 .partial_solution
3468 .add_decision(self.next, version);
3469 return;
3470 }
3471 self.pubgrub
3472 .add_incompatibility(Incompatibility::custom_term(
3473 self.next,
3474 Term::Positive(versions),
3475 UnavailableReason::Version(reason),
3476 ));
3477 }
3478
3479 fn with_env(mut self, env: ResolverEnvironment) -> Self {
3485 self.selected_versions.clear();
3486 self.env = env;
3487 if let Some(req) = self.env.narrow_python_requirement(&self.python_requirement) {
3489 debug!("Narrowed `requires-python` bound to: {}", req.target());
3490 self.python_requirement = req;
3491 }
3492 self
3493 }
3494
3495 fn source(
3499 &self,
3500 name: &PackageName,
3501 version: &Version,
3502 ) -> (Option<&VerbatimParsedUrl>, Option<&IndexUrl>) {
3503 let url = self.fork_urls.get(name);
3504 let index = url
3505 .is_none()
3506 .then(|| {
3507 self.pins
3508 .get(name, version)
3509 .expect("Every package should be pinned")
3510 .index()
3511 })
3512 .flatten();
3513 (url, index)
3514 }
3515
3516 fn into_resolution(self) -> Resolution {
3517 let solution: FxHashMap<_, _> = self.pubgrub.partial_solution.extract_solution().collect();
3518 let edge_count: usize = solution
3519 .keys()
3520 .map(|package| self.pubgrub.incompatibilities[package].len())
3521 .sum();
3522 let mut edges: Vec<ResolutionDependencyEdge> = Vec::with_capacity(edge_count);
3523 for (package, self_version) in &solution {
3524 for id in &self.pubgrub.incompatibilities[package] {
3525 let incompatibility = &self.pubgrub.incompatibility_store[*id];
3526 let pubgrub::Kind::FromDependencyOf(self_package, dependency_package) =
3527 &incompatibility.kind
3528 else {
3529 continue;
3530 };
3531 let (self_package, dependency_package) = (*self_package, *dependency_package);
3532 let Some((self_range, dependency_range)) =
3533 incompatibility.dependency_version_sets()
3534 else {
3535 continue;
3536 };
3537 let dependency_range =
3538 dependency_range.map_or_else(|| Cow::Owned(Range::empty()), Cow::Borrowed);
3539 if *package != self_package {
3540 continue;
3541 }
3542 if !self_range.contains(self_version) {
3543 continue;
3544 }
3545 let Some(dependency_version) = solution.get(&dependency_package) else {
3546 continue;
3547 };
3548 if !dependency_range.contains(dependency_version) {
3549 continue;
3550 }
3551
3552 let self_package = &self.pubgrub.package_store[self_package];
3553 let dependency_package = &self.pubgrub.package_store[dependency_package];
3554
3555 let (self_name, self_extra, self_group) = match &**self_package {
3556 PubGrubPackageInner::Package {
3557 name: self_name,
3558 extra: self_extra,
3559 group: self_group,
3560 marker: _,
3561 } => (Some(self_name), self_extra.as_ref(), self_group.as_ref()),
3562
3563 PubGrubPackageInner::Root(_) => (None, None, None),
3564
3565 _ => continue,
3566 };
3567
3568 let (self_url, self_index) = self_name
3569 .map(|self_name| self.source(self_name, self_version))
3570 .unwrap_or((None, None));
3571
3572 match **dependency_package {
3573 PubGrubPackageInner::Package {
3574 name: ref dependency_name,
3575 extra: ref dependency_extra,
3576 group: ref dependency_dev,
3577 marker: ref dependency_marker,
3578 } => {
3579 debug_assert!(
3580 dependency_extra.is_none(),
3581 "Packages should depend on an extra proxy"
3582 );
3583 debug_assert!(
3584 dependency_dev.is_none(),
3585 "Packages should depend on a group proxy"
3586 );
3587
3588 if self_group.is_none() {
3591 if self_name == Some(dependency_name) {
3592 continue;
3593 }
3594 }
3595
3596 let (to_url, to_index) = self.source(dependency_name, dependency_version);
3597
3598 let edge = ResolutionDependencyEdge {
3599 from: self_name.cloned(),
3600 from_version: self_version.clone(),
3601 from_url: self_url.cloned(),
3602 from_index: self_index.cloned(),
3603 from_extra: self_extra.cloned(),
3604 from_group: self_group.cloned(),
3605 to: dependency_name.clone(),
3606 to_version: dependency_version.clone(),
3607 to_url: to_url.cloned(),
3608 to_index: to_index.cloned(),
3609 to_extra: dependency_extra.clone(),
3610 to_group: dependency_dev.clone(),
3611 marker: *dependency_marker,
3612 };
3613 edges.push(edge);
3614 }
3615
3616 PubGrubPackageInner::Marker {
3617 name: ref dependency_name,
3618 marker: ref dependency_marker,
3619 } => {
3620 if self_group.is_none() {
3623 if self_name == Some(dependency_name) {
3624 continue;
3625 }
3626 }
3627
3628 let (to_url, to_index) = self.source(dependency_name, dependency_version);
3629
3630 let edge = ResolutionDependencyEdge {
3631 from: self_name.cloned(),
3632 from_version: self_version.clone(),
3633 from_url: self_url.cloned(),
3634 from_index: self_index.cloned(),
3635 from_extra: self_extra.cloned(),
3636 from_group: self_group.cloned(),
3637 to: dependency_name.clone(),
3638 to_version: dependency_version.clone(),
3639 to_url: to_url.cloned(),
3640 to_index: to_index.cloned(),
3641 to_extra: None,
3642 to_group: None,
3643 marker: *dependency_marker,
3644 };
3645 edges.push(edge);
3646 }
3647
3648 PubGrubPackageInner::Extra {
3649 name: ref dependency_name,
3650 extra: ref dependency_extra,
3651 marker: ref dependency_marker,
3652 } => {
3653 if self_group.is_none() {
3654 debug_assert!(
3655 self_name != Some(dependency_name),
3656 "Extras should be flattened"
3657 );
3658 }
3659 let (to_url, to_index) = self.source(dependency_name, dependency_version);
3660
3661 let edge = ResolutionDependencyEdge {
3663 from: self_name.cloned(),
3664 from_version: self_version.clone(),
3665 from_url: self_url.cloned(),
3666 from_index: self_index.cloned(),
3667 from_extra: self_extra.cloned(),
3668 from_group: self_group.cloned(),
3669 to: dependency_name.clone(),
3670 to_version: dependency_version.clone(),
3671 to_url: to_url.cloned(),
3672 to_index: to_index.cloned(),
3673 to_extra: Some(dependency_extra.clone()),
3674 to_group: None,
3675 marker: *dependency_marker,
3676 };
3677 edges.push(edge);
3678
3679 let edge = ResolutionDependencyEdge {
3681 from: self_name.cloned(),
3682 from_version: self_version.clone(),
3683 from_url: self_url.cloned(),
3684 from_index: self_index.cloned(),
3685 from_extra: self_extra.cloned(),
3686 from_group: self_group.cloned(),
3687 to: dependency_name.clone(),
3688 to_version: dependency_version.clone(),
3689 to_url: to_url.cloned(),
3690 to_index: to_index.cloned(),
3691 to_extra: None,
3692 to_group: None,
3693 marker: *dependency_marker,
3694 };
3695 edges.push(edge);
3696 }
3697
3698 PubGrubPackageInner::Group {
3699 name: ref dependency_name,
3700 group: ref dependency_group,
3701 marker: ref dependency_marker,
3702 } => {
3703 debug_assert!(
3704 self_name != Some(dependency_name),
3705 "Groups should be flattened"
3706 );
3707
3708 let (to_url, to_index) = self.source(dependency_name, dependency_version);
3709
3710 let edge = ResolutionDependencyEdge {
3713 from: self_name.cloned(),
3714 from_version: self_version.clone(),
3715 from_url: self_url.cloned(),
3716 from_index: self_index.cloned(),
3717 from_extra: self_extra.cloned(),
3718 from_group: self_group.cloned(),
3719 to: dependency_name.clone(),
3720 to_version: dependency_version.clone(),
3721 to_url: to_url.cloned(),
3722 to_index: to_index.cloned(),
3723 to_extra: None,
3724 to_group: Some(dependency_group.clone()),
3725 marker: *dependency_marker,
3726 };
3727 edges.push(edge);
3728 }
3729
3730 _ => {}
3731 }
3732 }
3733 }
3734
3735 let nodes = solution
3736 .into_iter()
3737 .filter_map(|(package, version)| {
3738 if let PubGrubPackageInner::Package {
3739 name,
3740 extra,
3741 group,
3742 marker: MarkerTree::TRUE,
3743 } = &*self.pubgrub.package_store[package]
3744 {
3745 let (url, index) = self.source(name, &version);
3746 Some((
3747 ResolutionPackage {
3748 name: name.clone(),
3749 extra: extra.clone(),
3750 dev: group.clone(),
3751 url: url.cloned(),
3752 index: index.cloned(),
3753 },
3754 version,
3755 ))
3756 } else {
3757 None
3758 }
3759 })
3760 .collect();
3761
3762 Resolution {
3763 nodes,
3764 edges,
3765 pins: self.pins,
3766 env: self.env,
3767 }
3768 }
3769}
3770
3771fn widen_to_gap(version: &Version, known_versions: Option<&[Version]>) -> Range<Version> {
3780 let versions = Range::singleton(version.clone());
3781 match known_versions {
3782 Some(known_versions) if !known_versions.is_empty() => {
3783 versions.widen_versions(known_versions)
3784 }
3785 _ => versions,
3786 }
3787}
3788
3789#[derive(Debug)]
3791#[expect(clippy::large_enum_variant)]
3792pub(crate) enum Request {
3793 Package(PackageName, Option<IndexMetadata>),
3795 Dist(Dist),
3797 Installed(InstalledDist),
3799 Prefetch(PackageName, Range<Version>, PythonRequirement),
3801}
3802
3803impl<'a> From<ResolvedDistRef<'a>> for Request {
3804 fn from(dist: ResolvedDistRef<'a>) -> Self {
3805 match dist {
3811 ResolvedDistRef::InstallableRegistrySourceDist { sdist, prioritized } => {
3812 let source = prioritized.source_dist().expect("a source distribution");
3815 assert_eq!(
3816 (&sdist.name, &sdist.version),
3817 (&source.name, &source.version),
3818 "expected chosen sdist to match prioritized sdist"
3819 );
3820 Self::Dist(Dist::Source(SourceDist::Registry(source)))
3821 }
3822 ResolvedDistRef::InstallableRegistryBuiltDist {
3823 wheel, prioritized, ..
3824 } => {
3825 assert_eq!(
3826 Some(&wheel.filename),
3827 prioritized.best_wheel().map(|(wheel, _)| &wheel.filename),
3828 "expected chosen wheel to match best wheel"
3829 );
3830 let built = prioritized.built_dist().expect("at least one wheel");
3833 Self::Dist(Dist::Built(BuiltDist::Registry(built)))
3834 }
3835 ResolvedDistRef::Installed { dist } => Self::Installed(dist.clone()),
3836 }
3837 }
3838}
3839
3840impl Display for Request {
3841 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
3842 match self {
3843 Self::Package(package_name, _) => {
3844 write!(f, "Versions {package_name}")
3845 }
3846 Self::Dist(dist) => {
3847 write!(f, "Metadata {dist}")
3848 }
3849 Self::Installed(dist) => {
3850 write!(f, "Installed metadata {dist}")
3851 }
3852 Self::Prefetch(package_name, range, _) => {
3853 write!(f, "Prefetch {package_name} {range}")
3854 }
3855 }
3856 }
3857}
3858
3859#[derive(Debug)]
3860#[expect(clippy::large_enum_variant)]
3861enum Response {
3862 Package(PackageName, Option<IndexUrl>, VersionsResponse),
3864 Dist {
3866 dist: Dist,
3867 metadata: MetadataResponse,
3868 },
3869 Installed {
3871 dist: InstalledDist,
3872 metadata: MetadataResponse,
3873 },
3874}
3875
3876enum Dependencies {
3882 Unavailable(UnavailableVersion),
3884 Available(Vec<PubGrubDependency>),
3890 RequiresPython(VersionSpecifiers),
3892 Unforkable(Vec<PubGrubDependency>),
3898}
3899
3900#[derive(Debug)]
3907enum ForkedDependencies {
3908 Unavailable(UnavailableVersion),
3910 Unforked(Vec<PubGrubDependency>),
3914 Forked {
3920 forks: Vec<Fork>,
3921 diverging_packages: BTreeSet<PackageName>,
3923 },
3924 RequiresPython(VersionSpecifiers),
3926}
3927
3928impl ForkedDependencies {
3929 fn from_dependencies_universal(
3936 dependencies: Dependencies,
3937 env: &ResolverEnvironment,
3938 python_requirement: &PythonRequirement,
3939 conflicts: &Conflicts,
3940 ) -> Self {
3941 let deps = match dependencies {
3942 Dependencies::Available(deps) => deps,
3943 Dependencies::Unforkable(deps) => return Self::Unforked(deps),
3944 Dependencies::RequiresPython(requires_python) => {
3945 return Self::RequiresPython(requires_python);
3946 }
3947 Dependencies::Unavailable(err) => return Self::Unavailable(err),
3948 };
3949 let mut name_to_deps: BTreeMap<PackageName, Vec<PubGrubDependency>> = BTreeMap::new();
3950 for dep in deps {
3951 let name = dep
3952 .package
3953 .name()
3954 .expect("dependency always has a name")
3955 .clone();
3956 name_to_deps.entry(name).or_default().push(dep);
3957 }
3958 let (mut forks, diverging_packages) =
3959 Self::fork(name_to_deps, env, python_requirement, conflicts);
3960 if forks.is_empty() {
3961 Self::Unforked(vec![])
3962 } else if forks.len() == 1 {
3963 Self::Unforked(forks.pop().unwrap().dependencies)
3964 } else {
3965 Self::Forked {
3966 forks,
3967 diverging_packages,
3968 }
3969 }
3970 }
3971
3972 fn from_dependencies_platform_specific(dependencies: Dependencies) -> Self {
3975 match dependencies {
3976 Dependencies::Available(deps) | Dependencies::Unforkable(deps) => Self::Unforked(deps),
3977 Dependencies::RequiresPython(requires_python) => Self::RequiresPython(requires_python),
3978 Dependencies::Unavailable(err) => Self::Unavailable(err),
3979 }
3980 }
3981
3982 fn fork(
3990 name_to_deps: BTreeMap<PackageName, Vec<PubGrubDependency>>,
3991 env: &ResolverEnvironment,
3992 python_requirement: &PythonRequirement,
3993 conflicts: &Conflicts,
3994 ) -> (Vec<Fork>, BTreeSet<PackageName>) {
3995 let python_marker = python_requirement.to_marker_tree();
3996
3997 let mut forks = vec![Fork::new(env.clone())];
3998 let mut diverging_packages = BTreeSet::new();
3999 for (name, mut deps) in name_to_deps {
4000 assert!(!deps.is_empty(), "every name has at least one dependency");
4001 if let [dep] = deps.as_slice() {
4012 if marker::requires_python(dep.package.marker())
4020 .is_none_or(|bound| !python_requirement.raises(&bound))
4021 {
4022 let dep = deps.pop().unwrap();
4023 let marker = dep.package.marker();
4024 for fork in &mut forks {
4025 if fork.env.included_by_marker(marker) {
4026 fork.add_dependency(dep.clone());
4027 }
4028 }
4029 continue;
4030 }
4031 } else {
4032 if let Some(dep) = deps.first() {
4034 let marker = dep.package.marker();
4035 if deps.iter().all(|dep| marker == dep.package.marker()) {
4036 if marker::requires_python(marker)
4040 .is_none_or(|bound| !python_requirement.raises(&bound))
4041 {
4042 for dep in deps {
4043 for fork in &mut forks {
4044 if fork.env.included_by_marker(marker) {
4045 fork.add_dependency(dep.clone());
4046 }
4047 }
4048 }
4049 continue;
4050 }
4051 }
4052 }
4053 }
4054 for dep in deps {
4055 let mut forker = match ForkingPossibility::new(env, &dep) {
4056 ForkingPossibility::Possible(forker) => forker,
4057 ForkingPossibility::DependencyAlwaysExcluded => {
4058 continue;
4061 }
4062 ForkingPossibility::NoForkingPossible => {
4063 for fork in &mut forks {
4066 fork.add_dependency(dep.clone());
4067 }
4068 continue;
4069 }
4070 };
4071 diverging_packages.insert(name.clone());
4073
4074 let mut new = vec![];
4075 for fork in std::mem::take(&mut forks) {
4076 let Some((remaining_forker, envs)) = forker.fork(&fork.env) else {
4077 new.push(fork);
4078 continue;
4079 };
4080 forker = remaining_forker;
4081
4082 for fork_env in envs {
4083 let mut new_fork = fork.clone();
4084 new_fork.set_env(fork_env);
4085 if forker.included(&new_fork.env) {
4090 new_fork.add_dependency(dep.clone());
4091 }
4092 if new_fork.env.included_by_marker(python_marker) {
4095 new.push(new_fork);
4096 }
4097 }
4098 }
4099 forks = new;
4100 }
4101 }
4102 for set in conflicts.iter() {
4117 let mut new = vec![];
4118 for fork in std::mem::take(&mut forks) {
4119 let mut has_conflicting_dependency = false;
4127 for item in set.iter() {
4128 if fork.contains_conflicting_item(item.as_ref()) {
4129 has_conflicting_dependency = true;
4130 diverging_packages.insert(item.package().clone());
4131 break;
4132 }
4133 }
4134 if !has_conflicting_dependency {
4135 new.push(fork);
4136 continue;
4137 }
4138
4139 let non_excluded: Vec<_> = set
4145 .iter()
4146 .filter(|item| fork.env.included_by_group(item.as_ref()))
4147 .collect();
4148 if non_excluded.len() < 2 {
4149 let dominated = non_excluded.iter().all(|item| {
4154 !conflicts.iter().any(|other_set| {
4155 !std::ptr::eq(set, other_set)
4156 && other_set.contains(item.package(), item.kind().as_ref())
4157 && other_set
4158 .iter()
4159 .filter(|other_item| {
4160 other_item.package() != item.package()
4161 || other_item.kind() != item.kind()
4162 })
4163 .any(|other_item| {
4164 fork.env.included_by_group(other_item.as_ref())
4165 })
4166 })
4167 });
4168 if dominated {
4169 let rules: Vec<_> = set
4174 .iter()
4175 .filter(|item| !fork.env.included_by_group(item.as_ref()))
4176 .cloned()
4177 .map(Err)
4178 .collect();
4179 if let Some(filtered) = fork.filter(rules) {
4180 new.push(filtered);
4181 }
4182 continue;
4183 }
4184 }
4185
4186 if let Some(fork_none) = fork.clone().filter(set.iter().cloned().map(Err)) {
4188 new.push(fork_none);
4189 }
4190
4191 for (i, _) in set.iter().enumerate() {
4199 let fork_allows_group = fork.clone().filter(
4200 set.iter()
4201 .cloned()
4202 .enumerate()
4203 .map(|(j, group)| if i == j { Ok(group) } else { Err(group) }),
4204 );
4205 if let Some(fork_allows_group) = fork_allows_group {
4206 new.push(fork_allows_group);
4207 }
4208 }
4209 }
4210 forks = new;
4211 }
4212 (forks, diverging_packages)
4213 }
4214}
4215
4216#[derive(Clone, Debug)]
4226struct Fork {
4227 dependencies: Vec<PubGrubDependency>,
4236 conflicts: crate::FxHashbrownSet<ConflictItem>,
4242 env: ResolverEnvironment,
4254}
4255
4256impl Fork {
4257 fn new(env: ResolverEnvironment) -> Self {
4260 Self {
4261 dependencies: vec![],
4262 conflicts: crate::FxHashbrownSet::default(),
4263 env,
4264 }
4265 }
4266
4267 fn add_dependency(&mut self, dep: PubGrubDependency) {
4269 if let Some(conflicting_item) = dep.conflicting_item() {
4270 self.conflicts.insert(conflicting_item.to_owned());
4271 }
4272 self.dependencies.push(dep);
4273 }
4274
4275 fn set_env(&mut self, env: ResolverEnvironment) {
4280 self.env = env;
4281 self.dependencies.retain(|dep| {
4282 let marker = dep.package.marker();
4283 if self.env.included_by_marker(marker) {
4284 return true;
4285 }
4286 if let Some(conflicting_item) = dep.conflicting_item() {
4287 self.conflicts.remove(&conflicting_item);
4288 }
4289 false
4290 });
4291 }
4292
4293 fn contains_conflicting_item(&self, item: ConflictItemRef<'_>) -> bool {
4296 self.conflicts.contains(&item)
4297 }
4298
4299 fn filter(
4306 mut self,
4307 rules: impl IntoIterator<Item = Result<ConflictItem, ConflictItem>>,
4308 ) -> Option<Self> {
4309 self.env = self.env.filter_by_group(rules)?;
4310 self.dependencies.retain(|dep| {
4311 let Some(conflicting_item) = dep.conflicting_item() else {
4312 return true;
4313 };
4314 if self.env.included_by_group(conflicting_item) {
4315 return true;
4316 }
4317 match conflicting_item.kind() {
4318 ConflictKindRef::Project => {
4321 if dep.parent.is_some() {
4322 return true;
4323 }
4324 }
4325 ConflictKindRef::Group(_) => {}
4326 ConflictKindRef::Extra(_) => {}
4327 }
4328 self.conflicts.remove(&conflicting_item);
4329 false
4330 });
4331 Some(self)
4332 }
4333
4334 fn cmp_requires_python(&self, other: &Self) -> Ordering {
4336 cmp_requires_python(&self.env, &other.env)
4337 }
4338
4339 fn cmp_upper_bounds(&self, other: &Self) -> Ordering {
4341 let self_upper_bounds = self
4346 .dependencies
4347 .iter()
4348 .filter(|dep| {
4349 dep.version
4350 .bounding_range()
4351 .is_some_and(|(_, upper)| !matches!(upper, Bound::Unbounded))
4352 })
4353 .count();
4354 let other_upper_bounds = other
4355 .dependencies
4356 .iter()
4357 .filter(|dep| {
4358 dep.version
4359 .bounding_range()
4360 .is_some_and(|(_, upper)| !matches!(upper, Bound::Unbounded))
4361 })
4362 .count();
4363
4364 self_upper_bounds.cmp(&other_upper_bounds)
4365 }
4366}
4367
4368fn cmp_requires_python(
4370 self_env: &ResolverEnvironment,
4371 other_env: &ResolverEnvironment,
4372) -> Ordering {
4373 let self_bound = self_env.requires_python().unwrap_or_default();
4383 let other_bound = other_env.requires_python().unwrap_or_default();
4384 self_bound.lower().cmp(other_bound.lower())
4385}
4386
4387impl Eq for Fork {}
4388
4389impl PartialEq for Fork {
4390 fn eq(&self, other: &Self) -> bool {
4391 self.dependencies == other.dependencies && self.env == other.env
4392 }
4393}
4394
4395#[derive(Debug, Clone)]
4396pub(crate) struct VersionFork {
4397 env: ResolverEnvironment,
4399 id: Id<PubGrubPackage>,
4401 version: Option<Version>,
4403}
4404
4405fn enrich_dependency_error(
4407 error: ResolveError,
4408 id: Id<PubGrubPackage>,
4409 version: &Version,
4410 pubgrub: &State<UvDependencyProvider>,
4411) -> ResolveError {
4412 let Some(name) = pubgrub.package_store[id].name_no_root() else {
4413 return error;
4414 };
4415 let chain = DerivationChainBuilder::from_state(id, version, pubgrub).unwrap_or_default();
4416 ResolveError::Dependencies(Box::new(error), name.clone(), version.clone(), chain)
4417}
4418
4419fn find_environments(id: Id<PubGrubPackage>, state: &State<UvDependencyProvider>) -> MarkerTree {
4421 let package = &state.package_store[id];
4422 if package.is_root() {
4423 return MarkerTree::TRUE;
4424 }
4425
4426 let mut ancestors = FxHashSet::default();
4429 let mut stack = vec![id];
4430 let mut root = None;
4431 ancestors.insert(id);
4432
4433 while let Some(current) = stack.pop() {
4434 let Some(incompatibilities) = state.incompatibilities.get(¤t) else {
4435 continue;
4436 };
4437
4438 for index in incompatibilities {
4439 let incompat = &state.incompatibility_store[*index];
4440 if let Kind::FromDependencyOf(parent, child) = &incompat.kind {
4441 if current != *child {
4442 continue;
4443 }
4444 if ancestors.insert(*parent) {
4445 if state.package_store[*parent].is_root() {
4446 root = Some(*parent);
4447 }
4448 stack.push(*parent);
4449 }
4450 }
4451 }
4452 }
4453
4454 let Some(root) = root else {
4455 return MarkerTree::FALSE;
4456 };
4457
4458 let mut environments = FxHashMap::default();
4461 let mut queue = VecDeque::from([root]);
4462 environments.insert(root, MarkerTree::TRUE);
4463
4464 while let Some(current) = queue.pop_front() {
4465 let Some(current_environment) = environments.get(¤t).copied() else {
4466 continue;
4467 };
4468 let Some(incompatibilities) = state.incompatibilities.get(¤t) else {
4469 continue;
4470 };
4471
4472 for index in incompatibilities {
4473 let incompat = &state.incompatibility_store[*index];
4474 let Kind::FromDependencyOf(parent, child) = &incompat.kind else {
4475 continue;
4476 };
4477 if current != *parent || !ancestors.contains(child) {
4478 continue;
4479 }
4480
4481 let mut next_environment = state.package_store[*child].marker();
4482 next_environment = next_environment.and(current_environment);
4483
4484 let entry = environments.entry(*child).or_insert(MarkerTree::FALSE);
4485 let mut combined = *entry;
4486 combined = combined.or(next_environment);
4487 if combined != *entry {
4488 *entry = combined;
4489 queue.push_back(*child);
4490 }
4491 }
4492 }
4493
4494 environments.remove(&id).unwrap_or(MarkerTree::FALSE)
4495}
4496
4497#[derive(Debug, Default, Clone)]
4498struct ConflictTracker {
4499 affected: FxHashMap<Id<PubGrubPackage>, usize>,
4501 prioritize: Vec<Id<PubGrubPackage>>,
4505 culprit: FxHashMap<Id<PubGrubPackage>, usize>,
4507 deprioritize: Vec<Id<PubGrubPackage>>,
4511}
4512
4513#[cfg(test)]
4514mod tests {
4515 use super::*;
4516
4517 fn versions(versions: &[&str]) -> Vec<Version> {
4518 versions
4519 .iter()
4520 .map(|version| version.parse().expect("valid version"))
4521 .collect()
4522 }
4523
4524 #[test]
4525 fn widens_a_version_to_its_gap() {
4526 let known_versions = versions(&["1.0", "2.0", "3.0"]);
4527 let version: Version = "2.0".parse().expect("valid version");
4528
4529 assert_eq!(
4531 widen_to_gap(&version, Some(&known_versions)).to_string(),
4532 ">1.0, <3.0"
4533 );
4534
4535 let version: Version = "1.0".parse().expect("valid version");
4537 assert_eq!(
4538 widen_to_gap(&version, Some(&known_versions)).to_string(),
4539 "<2.0"
4540 );
4541 let version: Version = "3.0".parse().expect("valid version");
4542 assert_eq!(
4543 widen_to_gap(&version, Some(&known_versions)).to_string(),
4544 ">2.0"
4545 );
4546 }
4547
4548 #[test]
4549 fn widens_a_version_without_known_versions_to_itself() {
4550 let version: Version = "2.0".parse().expect("valid version");
4551
4552 assert_eq!(
4554 widen_to_gap(&version, None),
4555 Range::singleton(version.clone())
4556 );
4557
4558 assert_eq!(widen_to_gap(&version, Some(&[])), Range::singleton(version));
4560 }
4561}