1use std::collections::{BTreeMap, BTreeSet, Bound};
2use std::fmt::Formatter;
3use std::sync::Arc;
4
5use indexmap::IndexSet;
6use itertools::Itertools;
7use owo_colors::OwoColorize;
8use pubgrub::{
9 DefaultStringReporter, DerivationTree, Derived, External, Range, Ranges, Reporter, Term,
10};
11use rustc_hash::FxHashMap;
12use tracing::trace;
13
14use uv_distribution_types::{
15 DerivationChain, DistErrorKind, IndexCapabilities, IndexLocations, IndexUrl, RequestedDist,
16};
17use uv_normalize::{ExtraName, InvalidNameError, PackageName};
18use uv_pep440::{LocalVersionSlice, LowerBound, Version};
19use uv_pep508::MarkerEnvironment;
20use uv_platform_tags::Tags;
21use uv_pypi_types::ParsedUrl;
22use uv_redacted::DisplaySafeUrl;
23use uv_static::EnvVars;
24
25use crate::candidate_selector::CandidateSelector;
26use crate::dependency_provider::UvDependencyProvider;
27use crate::fork_indexes::ForkIndexes;
28use crate::fork_urls::ForkUrls;
29use crate::prerelease::AllowPrerelease;
30use crate::pubgrub::{PubGrubPackage, PubGrubPackageInner, PubGrubReportFormatter};
31use crate::python_requirement::PythonRequirement;
32use crate::resolution::ConflictingDistributionError;
33use crate::resolver::{
34 MetadataUnavailable, ResolverEnvironment, UnavailablePackage, UnavailableReason,
35};
36use crate::{InMemoryIndex, Options};
37
38#[derive(Debug, thiserror::Error)]
39pub enum ResolveError {
40 #[error("Failed to resolve dependencies for package `{1}=={2}`")]
41 Dependencies(#[source] Box<Self>, PackageName, Version, DerivationChain),
42
43 #[error(transparent)]
44 Client(#[from] uv_client::Error),
45
46 #[error(transparent)]
47 Distribution(#[from] uv_distribution::Error),
48
49 #[error("The channel closed unexpectedly")]
50 ChannelClosed,
51
52 #[error("Attempted to wait on an unregistered task: `{_0}`")]
53 UnregisteredTask(String),
54
55 #[error(
56 "Requirements contain conflicting URLs for package `{package_name}`{}:\n- {}",
57 if env.marker_environment().is_some() {
58 String::new()
59 } else {
60 format!(" in {env}")
61 },
62 urls.iter()
63 .map(|url| format!("{}{}", DisplaySafeUrl::from(url.clone()), if url.is_editable() { " (editable)" } else { "" }))
64 .collect::<Vec<_>>()
65 .join("\n- ")
66 )]
67 ConflictingUrls {
68 package_name: PackageName,
69 urls: Vec<ParsedUrl>,
70 env: ResolverEnvironment,
71 },
72
73 #[error(
74 "Requirements contain conflicting indexes for package `{package_name}`{}:\n- {}",
75 if env.marker_environment().is_some() {
76 String::new()
77 } else {
78 format!(" in {env}")
79 },
80 indexes.iter()
81 .map(std::string::ToString::to_string)
82 .collect::<Vec<_>>()
83 .join("\n- ")
84 )]
85 ConflictingIndexesForEnvironment {
86 package_name: PackageName,
87 indexes: Vec<IndexUrl>,
88 env: ResolverEnvironment,
89 },
90
91 #[error("Requirements contain conflicting indexes for package `{0}`: `{1}` vs. `{2}`")]
92 ConflictingIndexes(PackageName, String, String),
93
94 #[error(
95 "Package `{name}` was included as a URL dependency. URL dependencies must be expressed as direct requirements or constraints. Consider adding `{requirement}` to your dependencies or constraints file.",
96 name = name.cyan(),
97 requirement = format!("{name} @ {url}").cyan(),
98 )]
99 DisallowedUrl { name: PackageName, url: String },
100
101 #[error(transparent)]
102 DistributionType(#[from] uv_distribution_types::Error),
103
104 #[error("{0} `{1}`")]
105 Dist(
106 DistErrorKind,
107 Box<RequestedDist>,
108 DerivationChain,
109 #[source] Arc<uv_distribution::Error>,
110 ),
111
112 #[error(transparent)]
113 NoSolution(#[from] Box<NoSolutionError>),
114
115 #[error("Attempted to construct an invalid version specifier")]
116 InvalidVersion(#[from] uv_pep440::VersionSpecifierBuildError),
117
118 #[error(
119 "In `--require-hashes` mode, all requirements must be pinned upfront with `==`, but found: `{0}`"
120 )]
121 UnhashedPackage(PackageName),
122
123 #[error("found conflicting distribution in resolution: {0}")]
124 ConflictingDistribution(ConflictingDistributionError),
125
126 #[error("Package `{0}` is unavailable")]
127 PackageUnavailable(PackageName),
128
129 #[error("Invalid extra value in conflict marker: {reason}: {raw_extra}")]
130 InvalidExtraInConflictMarker {
131 reason: String,
132 raw_extra: ExtraName,
133 },
134
135 #[error("Invalid {kind} value in conflict marker: {name_error}")]
136 InvalidValueInConflictMarker {
137 kind: &'static str,
138 #[source]
139 name_error: InvalidNameError,
140 },
141 #[error(
142 "The index returned metadata for the wrong package: expected {request} for {expected}, got {request} for {actual}"
143 )]
144 MismatchedPackageName {
145 request: &'static str,
146 expected: PackageName,
147 actual: PackageName,
148 },
149}
150
151impl<T> From<tokio::sync::mpsc::error::SendError<T>> for ResolveError {
152 fn from(_value: tokio::sync::mpsc::error::SendError<T>) -> Self {
155 Self::ChannelClosed
156 }
157}
158
159pub type ErrorTree = DerivationTree<PubGrubPackage, Range<Version>, UnavailableReason>;
160
161pub struct NoSolutionError {
163 error: pubgrub::NoSolutionError<UvDependencyProvider>,
164 index: InMemoryIndex,
165 included_versions: FxHashMap<PackageName, BTreeSet<Version>>,
169 available_versions: FxHashMap<PackageName, BTreeSet<Version>>,
177 available_indexes: FxHashMap<PackageName, BTreeSet<IndexUrl>>,
178 selector: CandidateSelector,
179 python_requirement: PythonRequirement,
180 index_locations: IndexLocations,
181 index_capabilities: IndexCapabilities,
182 unavailable_packages: FxHashMap<PackageName, UnavailablePackage>,
183 incomplete_packages: FxHashMap<PackageName, BTreeMap<Version, MetadataUnavailable>>,
184 fork_urls: ForkUrls,
185 fork_indexes: ForkIndexes,
186 env: ResolverEnvironment,
187 current_environment: MarkerEnvironment,
188 tags: Option<Tags>,
189 workspace_members: BTreeSet<PackageName>,
190 options: Options,
191}
192
193impl NoSolutionError {
194 pub(crate) fn new(
196 error: pubgrub::NoSolutionError<UvDependencyProvider>,
197 index: InMemoryIndex,
198 included_versions: FxHashMap<PackageName, BTreeSet<Version>>,
199 available_versions: FxHashMap<PackageName, BTreeSet<Version>>,
200 available_indexes: FxHashMap<PackageName, BTreeSet<IndexUrl>>,
201 selector: CandidateSelector,
202 python_requirement: PythonRequirement,
203 index_locations: IndexLocations,
204 index_capabilities: IndexCapabilities,
205 unavailable_packages: FxHashMap<PackageName, UnavailablePackage>,
206 incomplete_packages: FxHashMap<PackageName, BTreeMap<Version, MetadataUnavailable>>,
207 fork_urls: ForkUrls,
208 fork_indexes: ForkIndexes,
209 env: ResolverEnvironment,
210 current_environment: MarkerEnvironment,
211 tags: Option<Tags>,
212 workspace_members: BTreeSet<PackageName>,
213 options: Options,
214 ) -> Self {
215 Self {
216 error,
217 index,
218 included_versions,
219 available_versions,
220 available_indexes,
221 selector,
222 python_requirement,
223 index_locations,
224 index_capabilities,
225 unavailable_packages,
226 incomplete_packages,
227 fork_urls,
228 fork_indexes,
229 env,
230 current_environment,
231 tags,
232 workspace_members,
233 options,
234 }
235 }
236
237 pub(crate) fn collapse_proxies(derivation_tree: ErrorTree) -> ErrorTree {
240 fn collapse(derivation_tree: ErrorTree) -> Option<ErrorTree> {
241 match derivation_tree {
242 DerivationTree::Derived(derived) => {
243 match (&*derived.cause1, &*derived.cause2) {
244 (
245 DerivationTree::External(External::FromDependencyOf(package1, ..)),
246 DerivationTree::External(External::FromDependencyOf(package2, ..)),
247 ) if package1.is_proxy() && package2.is_proxy() => None,
248 (
249 DerivationTree::External(External::FromDependencyOf(package, ..)),
250 cause,
251 ) if package.is_proxy() => collapse(cause.clone()),
252 (
253 cause,
254 DerivationTree::External(External::FromDependencyOf(package, ..)),
255 ) if package.is_proxy() => collapse(cause.clone()),
256 (cause1, cause2) => {
257 let cause1 = collapse(cause1.clone());
258 let cause2 = collapse(cause2.clone());
259 match (cause1, cause2) {
260 (Some(cause1), Some(cause2)) => {
261 Some(DerivationTree::Derived(Derived {
262 cause1: Arc::new(cause1),
263 cause2: Arc::new(cause2),
264 ..derived
265 }))
266 }
267 (Some(cause), None) | (None, Some(cause)) => Some(cause),
268 _ => None,
269 }
270 }
271 }
272 }
273 DerivationTree::External(_) => Some(derivation_tree),
274 }
275 }
276
277 collapse(derivation_tree)
278 .expect("derivation tree should contain at least one external term")
279 }
280
281 pub(crate) fn collapse_local_version_segments(derivation_tree: ErrorTree) -> ErrorTree {
287 fn strip(derivation_tree: ErrorTree) -> Option<ErrorTree> {
288 match derivation_tree {
289 DerivationTree::External(External::NotRoot(_, _)) => Some(derivation_tree),
290 DerivationTree::External(External::NoVersions(package, versions)) => {
291 if SentinelRange::from(&versions).is_complement() {
292 return None;
293 }
294
295 let versions = SentinelRange::from(&versions).strip();
296 Some(DerivationTree::External(External::NoVersions(
297 package, versions,
298 )))
299 }
300 DerivationTree::External(External::FromDependencyOf(
301 package1,
302 versions1,
303 package2,
304 versions2,
305 )) => {
306 let versions1 = SentinelRange::from(&versions1).strip();
307 let versions2 = SentinelRange::from(&versions2).strip();
308 Some(DerivationTree::External(External::FromDependencyOf(
309 package1, versions1, package2, versions2,
310 )))
311 }
312 DerivationTree::External(External::Custom(package, versions, reason)) => {
313 let versions = SentinelRange::from(&versions).strip();
314 Some(DerivationTree::External(External::Custom(
315 package, versions, reason,
316 )))
317 }
318 DerivationTree::Derived(mut derived) => {
319 let cause1 = strip((*derived.cause1).clone());
320 let cause2 = strip((*derived.cause2).clone());
321 match (cause1, cause2) {
322 (Some(cause1), Some(cause2)) => Some(DerivationTree::Derived(Derived {
323 cause1: Arc::new(cause1),
324 cause2: Arc::new(cause2),
325 terms: std::mem::take(&mut derived.terms)
326 .into_iter()
327 .map(|(pkg, term)| {
328 let term = match term {
329 Term::Positive(versions) => {
330 Term::Positive(SentinelRange::from(&versions).strip())
331 }
332 Term::Negative(versions) => {
333 Term::Negative(SentinelRange::from(&versions).strip())
334 }
335 };
336 (pkg, term)
337 })
338 .collect(),
339 shared_id: derived.shared_id,
340 })),
341 (Some(cause), None) | (None, Some(cause)) => Some(cause),
342 _ => None,
343 }
344 }
345 }
346 }
347
348 strip(derivation_tree).expect("derivation tree should contain at least one term")
349 }
350
351 pub fn find_requires_python(&self) -> LowerBound {
353 fn find(derivation_tree: &ErrorTree, minimum: &mut LowerBound) {
354 match derivation_tree {
355 DerivationTree::Derived(derived) => {
356 find(derived.cause1.as_ref(), minimum);
357 find(derived.cause2.as_ref(), minimum);
358 }
359 DerivationTree::External(External::FromDependencyOf(.., package, version)) => {
360 if let PubGrubPackageInner::Python(_) = &**package {
361 if let Some((lower, ..)) = version.bounding_range() {
362 let lower = LowerBound::new(lower.cloned());
363 if lower > *minimum {
364 *minimum = lower;
365 }
366 }
367 }
368 }
369 DerivationTree::External(_) => {}
370 }
371 }
372
373 let mut minimum = LowerBound::default();
374 find(&self.error, &mut minimum);
375 minimum
376 }
377
378 pub fn header(&self) -> NoSolutionHeader {
380 NoSolutionHeader::new(self.env.clone())
381 }
382
383 pub fn derivation_tree(&self) -> &ErrorTree {
385 &self.error
386 }
387
388 pub fn packages(&self) -> impl Iterator<Item = &PackageName> {
390 self.error
391 .packages()
392 .into_iter()
393 .filter_map(|p| p.name())
394 .unique()
395 }
396}
397
398impl std::fmt::Debug for NoSolutionError {
399 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
400 let Self {
402 error,
403 index: _,
404 included_versions,
405 available_versions,
406 available_indexes,
407 selector,
408 python_requirement,
409 index_locations,
410 index_capabilities,
411 unavailable_packages,
412 incomplete_packages,
413 fork_urls,
414 fork_indexes,
415 env,
416 current_environment,
417 tags,
418 workspace_members,
419 options,
420 } = self;
421 f.debug_struct("NoSolutionError")
422 .field("error", error)
423 .field("included_versions", included_versions)
424 .field("available_versions", available_versions)
425 .field("available_indexes", available_indexes)
426 .field("selector", selector)
427 .field("python_requirement", python_requirement)
428 .field("index_locations", index_locations)
429 .field("index_capabilities", index_capabilities)
430 .field("unavailable_packages", unavailable_packages)
431 .field("incomplete_packages", incomplete_packages)
432 .field("fork_urls", fork_urls)
433 .field("fork_indexes", fork_indexes)
434 .field("env", env)
435 .field("current_environment", current_environment)
436 .field("tags", tags)
437 .field("workspace_members", workspace_members)
438 .field("options", options)
439 .finish()
440 }
441}
442
443impl std::error::Error for NoSolutionError {}
444
445impl std::fmt::Display for NoSolutionError {
446 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
447 let formatter = PubGrubReportFormatter {
449 included_versions: &self.included_versions,
450 available_versions: &self.available_versions,
451 python_requirement: &self.python_requirement,
452 workspace_members: &self.workspace_members,
453 tags: self.tags.as_ref(),
454 };
455
456 let mut tree = self.error.clone();
458 simplify_derivation_tree_markers(&self.python_requirement, &mut tree);
459 let should_display_tree = std::env::var_os(EnvVars::UV_INTERNAL__SHOW_DERIVATION_TREE)
460 .is_some()
461 || tracing::enabled!(tracing::Level::TRACE);
462
463 if should_display_tree {
464 display_tree(&tree, "Resolver derivation tree before reduction");
465 }
466
467 collapse_no_versions_of_workspace_members(&mut tree, &self.workspace_members);
468
469 if self.workspace_members.len() == 1 {
470 let project = self.workspace_members.iter().next().unwrap();
471 drop_root_dependency_on_project(&mut tree, project);
472 }
473
474 collapse_unavailable_versions(&mut tree);
475 collapse_redundant_depends_on_no_versions(&mut tree);
476
477 simplify_derivation_tree_ranges(
478 &mut tree,
479 &self.included_versions,
480 &self.selector,
481 &self.env,
482 );
483
484 collapse_redundant_no_versions(&mut tree);
486
487 while collapse_redundant_no_versions_tree(&mut tree) {
488 }
490
491 if should_display_tree {
492 display_tree(&tree, "Resolver derivation tree after reduction");
493 }
494
495 let report = DefaultStringReporter::report_with_formatter(&tree, &formatter);
496 write!(f, "{report}")?;
497
498 let mut additional_hints = IndexSet::default();
500 let inherited_exclude_newer_ranges = FxHashMap::default();
501 formatter.generate_hints(
502 &tree,
503 &self.index,
504 &self.selector,
505 &self.index_locations,
506 &self.index_capabilities,
507 &self.available_indexes,
508 &self.unavailable_packages,
509 &self.incomplete_packages,
510 &self.fork_urls,
511 &self.fork_indexes,
512 &self.env,
513 &self.current_environment,
514 self.tags.as_ref(),
515 &self.workspace_members,
516 &self.options,
517 &inherited_exclude_newer_ranges,
518 &mut additional_hints,
519 );
520 for hint in additional_hints {
521 write!(f, "\n\n{hint}")?;
522 }
523
524 Ok(())
525 }
526}
527
528#[expect(clippy::print_stderr)]
529fn display_tree(
530 error: &DerivationTree<PubGrubPackage, Range<Version>, UnavailableReason>,
531 name: &str,
532) {
533 let mut lines = Vec::new();
534 display_tree_inner(error, &mut lines, 0);
535 lines.reverse();
536
537 if std::env::var_os(EnvVars::UV_INTERNAL__SHOW_DERIVATION_TREE).is_some() {
538 eprintln!("{name}\n{}", lines.join("\n"));
539 } else {
540 trace!("{name}\n{}", lines.join("\n"));
541 }
542}
543
544fn display_tree_inner(
545 error: &DerivationTree<PubGrubPackage, Range<Version>, UnavailableReason>,
546 lines: &mut Vec<String>,
547 depth: usize,
548) {
549 let prefix = " ".repeat(depth);
550 match error {
551 DerivationTree::Derived(derived) => {
552 display_tree_inner(&derived.cause1, lines, depth + 1);
553 display_tree_inner(&derived.cause2, lines, depth + 1);
554 for (package, term) in &derived.terms {
555 match term {
556 Term::Positive(versions) => {
557 lines.push(format!("{prefix}term {package}{versions}"));
558 }
559 Term::Negative(versions) => {
560 lines.push(format!("{prefix}term not {package}{versions}"));
561 }
562 }
563 }
564 }
565 DerivationTree::External(external) => match external {
566 External::FromDependencyOf(package, version, dependency, dependency_version) => {
567 lines.push(format!(
568 "{prefix}{package}{version} depends on {dependency}{dependency_version}"
569 ));
570 }
571 External::Custom(package, versions, reason) => match reason {
572 UnavailableReason::Package(_) => {
573 lines.push(format!("{prefix}{package} {reason}"));
574 }
575 UnavailableReason::Version(_) => {
576 lines.push(format!("{prefix}{package}{versions} {reason}"));
577 }
578 },
579 External::NoVersions(package, versions) => {
580 lines.push(format!("{prefix}no versions of {package}{versions}"));
581 }
582 External::NotRoot(package, versions) => {
583 lines.push(format!("{prefix}not root {package}{versions}"));
584 }
585 },
586 }
587}
588
589fn collapse_redundant_no_versions(
590 tree: &mut DerivationTree<PubGrubPackage, Range<Version>, UnavailableReason>,
591) {
592 match tree {
593 DerivationTree::External(_) => {}
594 DerivationTree::Derived(derived) => {
595 match (
596 Arc::make_mut(&mut derived.cause1),
597 Arc::make_mut(&mut derived.cause2),
598 ) {
599 (
601 DerivationTree::External(External::NoVersions(package, versions)),
602 ref mut other,
603 )
604 | (
605 ref mut other,
606 DerivationTree::External(External::NoVersions(package, versions)),
607 ) => {
608 collapse_redundant_no_versions(other);
610
611 let package_terms = if let DerivationTree::Derived(derived) = other {
613 derived.terms.get(package)
614 } else {
615 derived.terms.get(package)
616 };
617
618 let Some(Term::Positive(term)) = package_terms else {
619 return;
620 };
621
622 let versions = versions.complement();
623
624 if versions.as_singleton().is_some() {
627 return;
628 }
629
630 if *term != Range::full() && *term != versions {
634 return;
635 }
636
637 *tree = other.clone();
638 }
639 _ => {
641 collapse_redundant_no_versions(Arc::make_mut(&mut derived.cause1));
642 collapse_redundant_no_versions(Arc::make_mut(&mut derived.cause2));
643 }
644 }
645 }
646 }
647}
648
649fn collapse_redundant_no_versions_tree(
684 tree: &mut DerivationTree<PubGrubPackage, Range<Version>, UnavailableReason>,
685) -> bool {
686 match tree {
687 DerivationTree::External(_) => false,
688 DerivationTree::Derived(derived) => {
689 match (
690 Arc::make_mut(&mut derived.cause1),
691 Arc::make_mut(&mut derived.cause2),
692 ) {
693 (
695 DerivationTree::External(External::NoVersions(package, versions)),
696 DerivationTree::External(External::NoVersions(other_package, other_versions)),
697 ) if package == other_package => {
698 let Some(Term::Positive(term)) = derived.terms.get(package) else {
700 return false;
701 };
702
703 if versions.subset_of(term) && other_versions.subset_of(term) {
705 *tree = DerivationTree::External(External::NoVersions(
706 package.clone(),
707 term.clone(),
708 ));
709 return true;
710 }
711
712 false
713 }
714 _ => {
716 collapse_redundant_no_versions_tree(Arc::make_mut(&mut derived.cause1))
717 || collapse_redundant_no_versions_tree(Arc::make_mut(&mut derived.cause2))
718 }
719 }
720 }
721 }
722}
723
724fn collapse_no_versions_of_workspace_members(
727 tree: &mut DerivationTree<PubGrubPackage, Range<Version>, UnavailableReason>,
728 workspace_members: &BTreeSet<PackageName>,
729) {
730 match tree {
731 DerivationTree::External(_) => {}
732 DerivationTree::Derived(derived) => {
733 match (
734 Arc::make_mut(&mut derived.cause1),
735 Arc::make_mut(&mut derived.cause2),
736 ) {
737 (DerivationTree::External(External::NoVersions(package, _)), ref mut other)
739 | (ref mut other, DerivationTree::External(External::NoVersions(package, _))) => {
740 collapse_no_versions_of_workspace_members(other, workspace_members);
742
743 let (PubGrubPackageInner::Package { name, .. }
745 | PubGrubPackageInner::Extra { name, .. }
746 | PubGrubPackageInner::Group { name, .. }) = &**package
747 else {
748 return;
749 };
750 if !workspace_members.contains(name) {
751 return;
752 }
753
754 *tree = other.clone();
756 }
757 _ => {
759 collapse_no_versions_of_workspace_members(
760 Arc::make_mut(&mut derived.cause1),
761 workspace_members,
762 );
763 collapse_no_versions_of_workspace_members(
764 Arc::make_mut(&mut derived.cause2),
765 workspace_members,
766 );
767 }
768 }
769 }
770 }
771}
772
773fn collapse_redundant_depends_on_no_versions(
795 tree: &mut DerivationTree<PubGrubPackage, Range<Version>, UnavailableReason>,
796) {
797 match tree {
798 DerivationTree::External(_) => {}
799 DerivationTree::Derived(derived) => {
800 match (
802 Arc::make_mut(&mut derived.cause1),
803 Arc::make_mut(&mut derived.cause2),
804 ) {
805 (
806 DerivationTree::External(External::FromDependencyOf(package, versions, _, _)),
807 ref mut other,
808 )
809 | (
810 ref mut other,
811 DerivationTree::External(External::FromDependencyOf(package, versions, _, _)),
812 ) => {
813 collapse_redundant_depends_on_no_versions_inner(other, package, versions);
815 }
816 _ => {
818 collapse_redundant_depends_on_no_versions(Arc::make_mut(&mut derived.cause1));
819 collapse_redundant_depends_on_no_versions(Arc::make_mut(&mut derived.cause2));
820 }
821 }
822 }
823 }
824}
825
826fn collapse_redundant_depends_on_no_versions_inner(
828 tree: &mut DerivationTree<PubGrubPackage, Range<Version>, UnavailableReason>,
829 package: &PubGrubPackage,
830 versions: &Range<Version>,
831) {
832 match tree {
833 DerivationTree::External(_) => {}
834 DerivationTree::Derived(derived) => {
835 match (&*derived.cause1, &*derived.cause2) {
837 (
838 DerivationTree::External(External::NoVersions(no_versions_package, _)),
839 dependency_clause @ DerivationTree::External(External::FromDependencyOf(
840 _,
841 _,
842 dependency_package,
843 dependency_versions,
844 )),
845 )
846 | (
847 dependency_clause @ DerivationTree::External(External::FromDependencyOf(
848 _,
849 _,
850 dependency_package,
851 dependency_versions,
852 )),
853 DerivationTree::External(External::NoVersions(no_versions_package, _)),
854 )
855 if no_versions_package == dependency_package
858 && package == no_versions_package
859 && versions.subset_of(dependency_versions) =>
861 {
862 *tree = dependency_clause.clone();
865
866 }
868 _ => {
870 collapse_redundant_depends_on_no_versions(Arc::make_mut(&mut derived.cause1));
871 collapse_redundant_depends_on_no_versions(Arc::make_mut(&mut derived.cause2));
872 }
873 }
874 }
875 }
876}
877
878fn simplify_derivation_tree_markers(
887 python_requirement: &PythonRequirement,
888 tree: &mut DerivationTree<PubGrubPackage, Range<Version>, UnavailableReason>,
889) {
890 match tree {
891 DerivationTree::External(External::NotRoot(pkg, _)) => {
892 pkg.simplify_markers(python_requirement);
893 }
894 DerivationTree::External(External::NoVersions(pkg, _)) => {
895 pkg.simplify_markers(python_requirement);
896 }
897 DerivationTree::External(External::FromDependencyOf(pkg1, _, pkg2, _)) => {
898 pkg1.simplify_markers(python_requirement);
899 pkg2.simplify_markers(python_requirement);
900 }
901 DerivationTree::External(External::Custom(pkg, _, _)) => {
902 pkg.simplify_markers(python_requirement);
903 }
904 DerivationTree::Derived(derived) => {
905 derived.terms = std::mem::take(&mut derived.terms)
906 .into_iter()
907 .map(|(mut pkg, term)| {
908 pkg.simplify_markers(python_requirement);
909 (pkg, term)
910 })
911 .collect();
912 simplify_derivation_tree_markers(
913 python_requirement,
914 Arc::make_mut(&mut derived.cause1),
915 );
916 simplify_derivation_tree_markers(
917 python_requirement,
918 Arc::make_mut(&mut derived.cause2),
919 );
920 }
921 }
922}
923
924fn collapse_unavailable_versions(
928 tree: &mut DerivationTree<PubGrubPackage, Range<Version>, UnavailableReason>,
929) {
930 match tree {
931 DerivationTree::External(_) => {}
932 DerivationTree::Derived(derived) => {
933 match (
934 Arc::make_mut(&mut derived.cause1),
935 Arc::make_mut(&mut derived.cause2),
936 ) {
937 (
939 DerivationTree::External(External::Custom(package, versions, reason)),
940 ref mut other,
941 )
942 | (
943 ref mut other,
944 DerivationTree::External(External::Custom(package, versions, reason)),
945 ) => {
946 collapse_unavailable_versions(other);
948
949 let DerivationTree::Derived(Derived {
951 terms,
952 shared_id,
953 cause1,
954 cause2,
955 }) = other
956 else {
957 return;
958 };
959
960 match (&**cause1, &**cause2) {
962 (
965 _,
966 DerivationTree::External(External::Custom(
967 other_package,
968 other_versions,
969 other_reason,
970 )),
971 ) => {
972 if package == other_package && reason == other_reason {
974 let versions = other_versions.union(versions);
976 let mut terms = terms.clone();
977 if let Some(Term::Positive(range)) = terms.get_mut(package) {
978 *range = versions.clone();
979 }
980 *tree = DerivationTree::Derived(Derived {
981 terms,
982 shared_id: *shared_id,
983 cause1: cause1.clone(),
984 cause2: Arc::new(DerivationTree::External(External::Custom(
985 package.clone(),
986 versions,
987 reason.clone(),
988 ))),
989 });
990 }
991 }
992 (
993 DerivationTree::External(External::Custom(
994 other_package,
995 other_versions,
996 other_reason,
997 )),
998 _,
999 ) => {
1000 if package == other_package && reason == other_reason {
1002 let versions = other_versions.union(versions);
1004 let mut terms = terms.clone();
1005 if let Some(Term::Positive(range)) = terms.get_mut(package) {
1006 *range = versions.clone();
1007 }
1008 *tree = DerivationTree::Derived(Derived {
1009 terms,
1010 shared_id: *shared_id,
1011 cause1: Arc::new(DerivationTree::External(External::Custom(
1012 package.clone(),
1013 versions,
1014 reason.clone(),
1015 ))),
1016 cause2: cause2.clone(),
1017 });
1018 }
1019 }
1020 _ => {}
1021 }
1022 }
1023 _ => {
1025 collapse_unavailable_versions(Arc::make_mut(&mut derived.cause1));
1026 collapse_unavailable_versions(Arc::make_mut(&mut derived.cause2));
1027 }
1028 }
1029 }
1030 }
1031}
1032
1033fn drop_root_dependency_on_project(
1041 tree: &mut DerivationTree<PubGrubPackage, Range<Version>, UnavailableReason>,
1042 project: &PackageName,
1043) {
1044 match tree {
1045 DerivationTree::External(_) => {}
1046 DerivationTree::Derived(derived) => {
1047 match (
1048 Arc::make_mut(&mut derived.cause1),
1049 Arc::make_mut(&mut derived.cause2),
1050 ) {
1051 (
1053 DerivationTree::External(External::FromDependencyOf(package, _, dependency, _)),
1054 ref mut other,
1055 )
1056 | (
1057 ref mut other,
1058 DerivationTree::External(External::FromDependencyOf(package, _, dependency, _)),
1059 ) => {
1060 if !matches!(&**package, PubGrubPackageInner::Root(_)) {
1062 return;
1063 }
1064
1065 let PubGrubPackageInner::Package { name, .. } = &**dependency else {
1067 return;
1068 };
1069 if name != project {
1070 return;
1071 }
1072
1073 drop_root_dependency_on_project(other, project);
1075
1076 *tree = other.clone();
1078 }
1079 _ => {
1081 drop_root_dependency_on_project(Arc::make_mut(&mut derived.cause1), project);
1082 drop_root_dependency_on_project(Arc::make_mut(&mut derived.cause2), project);
1083 }
1084 }
1085 }
1086 }
1087}
1088
1089#[derive(Debug)]
1091pub struct SentinelRange<'range>(&'range Range<Version>);
1092
1093impl<'range> From<&'range Range<Version>> for SentinelRange<'range> {
1094 fn from(range: &'range Range<Version>) -> Self {
1095 Self(range)
1096 }
1097}
1098
1099impl SentinelRange<'_> {
1100 pub fn is_sentinel(&self) -> bool {
1102 self.0.iter().all(|(lower, upper)| {
1103 let (Bound::Included(lower), Bound::Excluded(upper)) = (lower, upper) else {
1104 return false;
1105 };
1106 if !lower.local().is_empty() {
1107 return false;
1108 }
1109 if upper.local() != LocalVersionSlice::Max {
1110 return false;
1111 }
1112 *lower == upper.clone().without_local()
1113 })
1114 }
1115
1116 pub fn is_complement(&self) -> bool {
1119 self.0.iter().all(|(lower, upper)| {
1120 let (Bound::Excluded(lower), Bound::Excluded(upper)) = (lower, upper) else {
1121 return false;
1122 };
1123 if !lower.local().is_empty() {
1124 return false;
1125 }
1126 if upper.local() != LocalVersionSlice::Max {
1127 return false;
1128 }
1129 *lower == upper.clone().without_local()
1130 })
1131 }
1132
1133 pub fn strip(&self) -> Ranges<Version> {
1135 self.0
1136 .iter()
1137 .map(|(lower, upper)| Self::strip_sentinel(lower.clone(), upper.clone()))
1138 .collect()
1139 }
1140
1141 fn strip_sentinel(
1143 mut lower: Bound<Version>,
1144 mut upper: Bound<Version>,
1145 ) -> (Bound<Version>, Bound<Version>) {
1146 match (&lower, &upper) {
1147 (Bound::Unbounded, Bound::Unbounded) => {}
1148 (Bound::Unbounded, Bound::Included(v)) => {
1149 if v.local() == LocalVersionSlice::Max {
1151 upper = Bound::Included(v.clone().without_local());
1152 }
1153 }
1154 (Bound::Unbounded, Bound::Excluded(v)) => {
1155 if v.local() == LocalVersionSlice::Max {
1157 upper = Bound::Excluded(v.clone().without_local());
1158 }
1159 }
1160 (Bound::Included(v), Bound::Unbounded) => {
1161 if v.local() == LocalVersionSlice::Max {
1163 lower = Bound::Excluded(v.clone().without_local());
1164 }
1165 }
1166 (Bound::Included(v), Bound::Included(b)) => {
1167 if v.local() == LocalVersionSlice::Max {
1169 lower = Bound::Excluded(v.clone().without_local());
1170 }
1171 if b.local() == LocalVersionSlice::Max {
1173 upper = Bound::Included(b.clone().without_local());
1174 }
1175 }
1176 (Bound::Included(v), Bound::Excluded(b)) => {
1177 if v.local() == LocalVersionSlice::Max {
1179 lower = Bound::Excluded(v.clone().without_local());
1180 }
1181 if b.local() == LocalVersionSlice::Max {
1183 upper = Bound::Included(b.clone().without_local());
1184 }
1185 }
1186 (Bound::Excluded(v), Bound::Unbounded) => {
1187 if v.local() == LocalVersionSlice::Max {
1189 lower = Bound::Excluded(v.clone().without_local());
1190 }
1191 }
1192 (Bound::Excluded(v), Bound::Included(b)) => {
1193 if v.local() == LocalVersionSlice::Max {
1195 lower = Bound::Excluded(v.clone().without_local());
1196 }
1197 if b.local() == LocalVersionSlice::Max {
1199 upper = Bound::Included(b.clone().without_local());
1200 }
1201 }
1202 (Bound::Excluded(v), Bound::Excluded(b)) => {
1203 if v.local() == LocalVersionSlice::Max {
1205 lower = Bound::Excluded(v.clone().without_local());
1206 }
1207 if b.local() == LocalVersionSlice::Max {
1209 upper = Bound::Excluded(b.clone().without_local());
1210 }
1211 }
1212 }
1213 (lower, upper)
1214 }
1215}
1216
1217#[derive(Debug, Clone, PartialEq, Eq)]
1219pub(crate) struct PrefixMatch<'a> {
1220 version: &'a Version,
1221}
1222
1223impl<'a> PrefixMatch<'a> {
1224 pub(crate) fn from_range(lower: &'a Bound<Version>, upper: &'a Bound<Version>) -> Option<Self> {
1229 let Bound::Included(lower) = lower else {
1230 return None;
1231 };
1232 let Bound::Excluded(upper) = upper else {
1233 return None;
1234 };
1235 if lower.is_pre() || lower.is_post() || lower.is_local() {
1236 return None;
1237 }
1238 if upper.is_pre() || upper.is_post() || upper.is_local() {
1239 return None;
1240 }
1241 if lower.dev() != Some(0) {
1242 return None;
1243 }
1244 if upper.dev() != Some(0) {
1245 return None;
1246 }
1247 if lower.release().len() != upper.release().len() {
1248 return None;
1249 }
1250
1251 let num_segments = lower.release().len();
1253 for (i, (lower, upper)) in lower
1254 .release()
1255 .iter()
1256 .zip(upper.release().iter())
1257 .enumerate()
1258 {
1259 if i == num_segments - 1 {
1260 if lower + 1 != *upper {
1261 return None;
1262 }
1263 } else {
1264 if lower != upper {
1265 return None;
1266 }
1267 }
1268 }
1269
1270 Some(PrefixMatch { version: lower })
1271 }
1272}
1273
1274impl std::fmt::Display for PrefixMatch<'_> {
1275 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1276 write!(f, "=={}.*", self.version.only_release())
1277 }
1278}
1279
1280#[derive(Debug)]
1281pub struct NoSolutionHeader {
1282 env: ResolverEnvironment,
1284 context: Option<&'static str>,
1286}
1287
1288impl NoSolutionHeader {
1289 pub fn new(env: ResolverEnvironment) -> Self {
1291 Self { env, context: None }
1292 }
1293
1294 #[must_use]
1296 pub fn with_context(mut self, context: &'static str) -> Self {
1297 self.context = Some(context);
1298 self
1299 }
1300}
1301
1302impl std::fmt::Display for NoSolutionHeader {
1303 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1304 match (self.context, self.env.end_user_fork_display()) {
1305 (None, None) => write!(f, "No solution found when resolving dependencies:"),
1306 (Some(context), None) => write!(
1307 f,
1308 "No solution found when resolving {context} dependencies:"
1309 ),
1310 (None, Some(split)) => write!(
1311 f,
1312 "No solution found when resolving dependencies for {split}:"
1313 ),
1314 (Some(context), Some(split)) => write!(
1315 f,
1316 "No solution found when resolving {context} dependencies for {split}:"
1317 ),
1318 }
1319 }
1320}
1321
1322fn simplify_derivation_tree_ranges(
1325 tree: &mut DerivationTree<PubGrubPackage, Range<Version>, UnavailableReason>,
1326 included_versions: &FxHashMap<PackageName, BTreeSet<Version>>,
1327 candidate_selector: &CandidateSelector,
1328 resolver_environment: &ResolverEnvironment,
1329) {
1330 match tree {
1331 DerivationTree::External(external) => match external {
1332 External::FromDependencyOf(package1, versions1, package2, versions2) => {
1333 if let Some(simplified) = simplify_range(
1334 versions1,
1335 package1,
1336 included_versions,
1337 candidate_selector,
1338 resolver_environment,
1339 ) {
1340 *versions1 = simplified;
1341 }
1342 if let Some(simplified) = simplify_range(
1343 versions2,
1344 package2,
1345 included_versions,
1346 candidate_selector,
1347 resolver_environment,
1348 ) {
1349 *versions2 = simplified;
1350 }
1351 }
1352 External::NoVersions(package, versions) => {
1353 if let Some(simplified) = simplify_range(
1354 versions,
1355 package,
1356 included_versions,
1357 candidate_selector,
1358 resolver_environment,
1359 ) {
1360 *versions = simplified;
1361 }
1362 }
1363 External::Custom(package, versions, _) => {
1364 if let Some(simplified) = simplify_range(
1365 versions,
1366 package,
1367 included_versions,
1368 candidate_selector,
1369 resolver_environment,
1370 ) {
1371 *versions = simplified;
1372 }
1373 }
1374 External::NotRoot(..) => (),
1375 },
1376 DerivationTree::Derived(derived) => {
1377 simplify_derivation_tree_ranges(
1379 Arc::make_mut(&mut derived.cause1),
1380 included_versions,
1381 candidate_selector,
1382 resolver_environment,
1383 );
1384 simplify_derivation_tree_ranges(
1385 Arc::make_mut(&mut derived.cause2),
1386 included_versions,
1387 candidate_selector,
1388 resolver_environment,
1389 );
1390
1391 derived.terms = std::mem::take(&mut derived.terms)
1393 .into_iter()
1394 .map(|(package, term)| {
1395 let term = match term {
1396 Term::Positive(versions) => Term::Positive(
1397 simplify_range(
1398 &versions,
1399 &package,
1400 included_versions,
1401 candidate_selector,
1402 resolver_environment,
1403 )
1404 .unwrap_or(versions),
1405 ),
1406 Term::Negative(versions) => Term::Negative(
1407 simplify_range(
1408 &versions,
1409 &package,
1410 included_versions,
1411 candidate_selector,
1412 resolver_environment,
1413 )
1414 .unwrap_or(versions),
1415 ),
1416 };
1417 (package, term)
1418 })
1419 .collect();
1420 }
1421 }
1422}
1423
1424fn simplify_range(
1428 range: &Range<Version>,
1429 package: &PubGrubPackage,
1430 included_versions: &FxHashMap<PackageName, BTreeSet<Version>>,
1431 candidate_selector: &CandidateSelector,
1432 resolver_environment: &ResolverEnvironment,
1433) -> Option<Range<Version>> {
1434 let name = package.name()?;
1436 let versions = included_versions.get(name)?;
1437
1438 if range == &Range::full() {
1440 return None;
1441 }
1442
1443 if let Some(version) = versions.iter().next() {
1445 if versions.len() == 1 && range.contains(version) {
1446 return Some(Range::singleton(version.clone()));
1447 }
1448 }
1449
1450 let prereleases_not_allowed = candidate_selector
1452 .prerelease_strategy()
1453 .allows(name, resolver_environment)
1454 != AllowPrerelease::Yes;
1455
1456 let any_prerelease = range.iter().any(|(start, end)| {
1457 let is_pre1 = match start {
1458 Bound::Included(version) => version.any_prerelease(),
1459 Bound::Excluded(version) => version.any_prerelease(),
1460 Bound::Unbounded => false,
1461 };
1462 let is_pre2 = match end {
1463 Bound::Included(version) => version.any_prerelease(),
1464 Bound::Excluded(version) => version.any_prerelease(),
1465 Bound::Unbounded => false,
1466 };
1467 is_pre1 || is_pre2
1468 });
1469
1470 Some(range.simplify(versions.iter().filter(|version| {
1472 if any_prerelease {
1474 return true;
1475 }
1476
1477 if prereleases_not_allowed && version.any_prerelease() {
1479 return false;
1480 }
1481
1482 true
1484 })))
1485}