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, VersionSpecifier};
19use uv_pep508::{MarkerEnvironment, MarkerExpression, MarkerTree, MarkerValueVersion};
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(
42 #[source] Box<ResolveError>,
43 PackageName,
44 Version,
45 DerivationChain,
46 ),
47
48 #[error(transparent)]
49 Client(#[from] uv_client::Error),
50
51 #[error(transparent)]
52 Distribution(#[from] uv_distribution::Error),
53
54 #[error("The channel closed unexpectedly")]
55 ChannelClosed,
56
57 #[error(transparent)]
58 Join(#[from] tokio::task::JoinError),
59
60 #[error("Attempted to wait on an unregistered task: `{_0}`")]
61 UnregisteredTask(String),
62
63 #[error(
64 "Requirements contain conflicting URLs for package `{package_name}`{}:\n- {}",
65 if env.marker_environment().is_some() {
66 String::new()
67 } else {
68 format!(" in {env}")
69 },
70 urls.iter()
71 .map(|url| format!("{}{}", DisplaySafeUrl::from(url.clone()), if url.is_editable() { " (editable)" } else { "" }))
72 .collect::<Vec<_>>()
73 .join("\n- ")
74 )]
75 ConflictingUrls {
76 package_name: PackageName,
77 urls: Vec<ParsedUrl>,
78 env: ResolverEnvironment,
79 },
80
81 #[error(
82 "Requirements contain conflicting indexes for package `{package_name}`{}:\n- {}",
83 if env.marker_environment().is_some() {
84 String::new()
85 } else {
86 format!(" in {env}")
87 },
88 indexes.iter()
89 .map(std::string::ToString::to_string)
90 .collect::<Vec<_>>()
91 .join("\n- ")
92 )]
93 ConflictingIndexesForEnvironment {
94 package_name: PackageName,
95 indexes: Vec<IndexUrl>,
96 env: ResolverEnvironment,
97 },
98
99 #[error("Requirements contain conflicting indexes for package `{0}`: `{1}` vs. `{2}`")]
100 ConflictingIndexes(PackageName, String, String),
101
102 #[error(
103 "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.",
104 name = name.cyan(),
105 requirement = format!("{name} @ {url}").cyan(),
106 )]
107 DisallowedUrl { name: PackageName, url: String },
108
109 #[error(transparent)]
110 DistributionType(#[from] uv_distribution_types::Error),
111
112 #[error(transparent)]
113 ParsedUrl(#[from] uv_pypi_types::ParsedUrlError),
114
115 #[error("{0} `{1}`")]
116 Dist(
117 DistErrorKind,
118 Box<RequestedDist>,
119 DerivationChain,
120 #[source] Arc<uv_distribution::Error>,
121 ),
122
123 #[error(transparent)]
124 NoSolution(#[from] Box<NoSolutionError>),
125
126 #[error("Attempted to construct an invalid version specifier")]
127 InvalidVersion(#[from] uv_pep440::VersionSpecifierBuildError),
128
129 #[error(
130 "In `--require-hashes` mode, all requirements must be pinned upfront with `==`, but found: `{0}`"
131 )]
132 UnhashedPackage(PackageName),
133
134 #[error("found conflicting distribution in resolution: {0}")]
135 ConflictingDistribution(ConflictingDistributionError),
136
137 #[error("Package `{0}` is unavailable")]
138 PackageUnavailable(PackageName),
139
140 #[error("Invalid extra value in conflict marker: {reason}: {raw_extra}")]
141 InvalidExtraInConflictMarker {
142 reason: String,
143 raw_extra: ExtraName,
144 },
145
146 #[error("Invalid {kind} value in conflict marker: {name_error}")]
147 InvalidValueInConflictMarker {
148 kind: &'static str,
149 #[source]
150 name_error: InvalidNameError,
151 },
152 #[error(
153 "The index returned metadata for the wrong package: expected {request} for {expected}, got {request} for {actual}"
154 )]
155 MismatchedPackageName {
156 request: &'static str,
157 expected: PackageName,
158 actual: PackageName,
159 },
160}
161
162impl<T> From<tokio::sync::mpsc::error::SendError<T>> for ResolveError {
163 fn from(_value: tokio::sync::mpsc::error::SendError<T>) -> Self {
166 Self::ChannelClosed
167 }
168}
169
170pub type ErrorTree = DerivationTree<PubGrubPackage, Range<Version>, UnavailableReason>;
171
172pub struct NoSolutionError {
174 error: pubgrub::NoSolutionError<UvDependencyProvider>,
175 index: InMemoryIndex,
176 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 available_versions: FxHashMap<PackageName, BTreeSet<Version>>,
199 available_indexes: FxHashMap<PackageName, BTreeSet<IndexUrl>>,
200 selector: CandidateSelector,
201 python_requirement: PythonRequirement,
202 index_locations: IndexLocations,
203 index_capabilities: IndexCapabilities,
204 unavailable_packages: FxHashMap<PackageName, UnavailablePackage>,
205 incomplete_packages: FxHashMap<PackageName, BTreeMap<Version, MetadataUnavailable>>,
206 fork_urls: ForkUrls,
207 fork_indexes: ForkIndexes,
208 env: ResolverEnvironment,
209 current_environment: MarkerEnvironment,
210 tags: Option<Tags>,
211 workspace_members: BTreeSet<PackageName>,
212 options: Options,
213 ) -> Self {
214 Self {
215 error,
216 index,
217 available_versions,
218 available_indexes,
219 selector,
220 python_requirement,
221 index_locations,
222 index_capabilities,
223 unavailable_packages,
224 incomplete_packages,
225 fork_urls,
226 fork_indexes,
227 env,
228 current_environment,
229 tags,
230 workspace_members,
231 options,
232 }
233 }
234
235 pub(crate) fn collapse_proxies(derivation_tree: ErrorTree) -> ErrorTree {
238 fn collapse(derivation_tree: ErrorTree) -> Option<ErrorTree> {
239 match derivation_tree {
240 DerivationTree::Derived(derived) => {
241 match (&*derived.cause1, &*derived.cause2) {
242 (
243 DerivationTree::External(External::FromDependencyOf(package1, ..)),
244 DerivationTree::External(External::FromDependencyOf(package2, ..)),
245 ) if package1.is_proxy() && package2.is_proxy() => None,
246 (
247 DerivationTree::External(External::FromDependencyOf(package, ..)),
248 cause,
249 ) if package.is_proxy() => collapse(cause.clone()),
250 (
251 cause,
252 DerivationTree::External(External::FromDependencyOf(package, ..)),
253 ) if package.is_proxy() => collapse(cause.clone()),
254 (cause1, cause2) => {
255 let cause1 = collapse(cause1.clone());
256 let cause2 = collapse(cause2.clone());
257 match (cause1, cause2) {
258 (Some(cause1), Some(cause2)) => {
259 Some(DerivationTree::Derived(Derived {
260 cause1: Arc::new(cause1),
261 cause2: Arc::new(cause2),
262 ..derived
263 }))
264 }
265 (Some(cause), None) | (None, Some(cause)) => Some(cause),
266 _ => None,
267 }
268 }
269 }
270 }
271 DerivationTree::External(_) => Some(derivation_tree),
272 }
273 }
274
275 collapse(derivation_tree)
276 .expect("derivation tree should contain at least one external term")
277 }
278
279 pub(crate) fn collapse_local_version_segments(derivation_tree: ErrorTree) -> ErrorTree {
285 fn strip(derivation_tree: ErrorTree) -> Option<ErrorTree> {
286 match derivation_tree {
287 DerivationTree::External(External::NotRoot(_, _)) => Some(derivation_tree),
288 DerivationTree::External(External::NoVersions(package, versions)) => {
289 if SentinelRange::from(&versions).is_complement() {
290 return None;
291 }
292
293 let versions = SentinelRange::from(&versions).strip();
294 Some(DerivationTree::External(External::NoVersions(
295 package, versions,
296 )))
297 }
298 DerivationTree::External(External::FromDependencyOf(
299 package1,
300 versions1,
301 package2,
302 versions2,
303 )) => {
304 let versions1 = SentinelRange::from(&versions1).strip();
305 let versions2 = SentinelRange::from(&versions2).strip();
306 Some(DerivationTree::External(External::FromDependencyOf(
307 package1, versions1, package2, versions2,
308 )))
309 }
310 DerivationTree::External(External::Custom(package, versions, reason)) => {
311 let versions = SentinelRange::from(&versions).strip();
312 Some(DerivationTree::External(External::Custom(
313 package, versions, reason,
314 )))
315 }
316 DerivationTree::Derived(mut derived) => {
317 let cause1 = strip((*derived.cause1).clone());
318 let cause2 = strip((*derived.cause2).clone());
319 match (cause1, cause2) {
320 (Some(cause1), Some(cause2)) => Some(DerivationTree::Derived(Derived {
321 cause1: Arc::new(cause1),
322 cause2: Arc::new(cause2),
323 terms: std::mem::take(&mut derived.terms)
324 .into_iter()
325 .map(|(pkg, term)| {
326 let term = match term {
327 Term::Positive(versions) => {
328 Term::Positive(SentinelRange::from(&versions).strip())
329 }
330 Term::Negative(versions) => {
331 Term::Negative(SentinelRange::from(&versions).strip())
332 }
333 };
334 (pkg, term)
335 })
336 .collect(),
337 shared_id: derived.shared_id,
338 })),
339 (Some(cause), None) | (None, Some(cause)) => Some(cause),
340 _ => None,
341 }
342 }
343 }
344 }
345
346 strip(derivation_tree).expect("derivation tree should contain at least one term")
347 }
348
349 pub fn find_requires_python(&self) -> LowerBound {
351 fn find(derivation_tree: &ErrorTree, minimum: &mut LowerBound) {
352 match derivation_tree {
353 DerivationTree::Derived(derived) => {
354 find(derived.cause1.as_ref(), minimum);
355 find(derived.cause2.as_ref(), minimum);
356 }
357 DerivationTree::External(External::FromDependencyOf(.., package, version)) => {
358 if let PubGrubPackageInner::Python(_) = &**package {
359 if let Some((lower, ..)) = version.bounding_range() {
360 let lower = LowerBound::new(lower.cloned());
361 if lower > *minimum {
362 *minimum = lower;
363 }
364 }
365 }
366 }
367 DerivationTree::External(_) => {}
368 }
369 }
370
371 let mut minimum = LowerBound::default();
372 find(&self.error, &mut minimum);
373 minimum
374 }
375
376 pub fn header(&self) -> NoSolutionHeader {
378 NoSolutionHeader::new(self.env.clone())
379 }
380
381 pub fn derivation_tree(&self) -> &ErrorTree {
383 &self.error
384 }
385
386 fn hint_disjoint_targets(&self, f: &mut Formatter) -> std::fmt::Result {
389 let Some(markers) = self.env.fork_markers() else {
391 return Ok(());
392 };
393
394 let current_python_version = self.current_environment.python_version().version.clone();
397 let current_python_marker = MarkerTree::expression(MarkerExpression::Version {
398 key: MarkerValueVersion::PythonVersion,
399 specifier: VersionSpecifier::equals_version(current_python_version.clone()),
400 });
401 if markers.is_disjoint(current_python_marker) {
402 write!(
403 f,
404 "\n\n{}{} While the active Python version is {}, \
405 the resolution failed for other Python versions supported by your \
406 project. Consider limiting your project's supported Python versions \
407 using `requires-python`.",
408 "hint".bold().cyan(),
409 ":".bold(),
410 current_python_version,
411 )?;
412 } else if !markers.evaluate(&self.current_environment, &[]) {
413 write!(
414 f,
415 "\n\n{}{} The resolution failed for an environment that is not the current one, \
416 consider limiting the environments with `tool.uv.environments`.",
417 "hint".bold().cyan(),
418 ":".bold(),
419 )?;
420 }
421 Ok(())
422 }
423
424 pub fn packages(&self) -> impl Iterator<Item = &PackageName> {
426 self.error
427 .packages()
428 .into_iter()
429 .filter_map(|p| p.name())
430 .unique()
431 }
432}
433
434impl std::fmt::Debug for NoSolutionError {
435 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
436 let Self {
438 error,
439 index: _,
440 available_versions,
441 available_indexes,
442 selector,
443 python_requirement,
444 index_locations,
445 index_capabilities,
446 unavailable_packages,
447 incomplete_packages,
448 fork_urls,
449 fork_indexes,
450 env,
451 current_environment,
452 tags,
453 workspace_members,
454 options,
455 } = self;
456 f.debug_struct("NoSolutionError")
457 .field("error", error)
458 .field("available_versions", available_versions)
459 .field("available_indexes", available_indexes)
460 .field("selector", selector)
461 .field("python_requirement", python_requirement)
462 .field("index_locations", index_locations)
463 .field("index_capabilities", index_capabilities)
464 .field("unavailable_packages", unavailable_packages)
465 .field("incomplete_packages", incomplete_packages)
466 .field("fork_urls", fork_urls)
467 .field("fork_indexes", fork_indexes)
468 .field("env", env)
469 .field("current_environment", current_environment)
470 .field("tags", tags)
471 .field("workspace_members", workspace_members)
472 .field("options", options)
473 .finish()
474 }
475}
476
477impl std::error::Error for NoSolutionError {}
478
479impl std::fmt::Display for NoSolutionError {
480 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
481 let formatter = PubGrubReportFormatter {
483 available_versions: &self.available_versions,
484 python_requirement: &self.python_requirement,
485 workspace_members: &self.workspace_members,
486 tags: self.tags.as_ref(),
487 };
488
489 let mut tree = self.error.clone();
491 simplify_derivation_tree_markers(&self.python_requirement, &mut tree);
492 let should_display_tree = std::env::var_os(EnvVars::UV_INTERNAL__SHOW_DERIVATION_TREE)
493 .is_some()
494 || tracing::enabled!(tracing::Level::TRACE);
495
496 if should_display_tree {
497 display_tree(&tree, "Resolver derivation tree before reduction");
498 }
499
500 collapse_no_versions_of_workspace_members(&mut tree, &self.workspace_members);
501
502 if self.workspace_members.len() == 1 {
503 let project = self.workspace_members.iter().next().unwrap();
504 drop_root_dependency_on_project(&mut tree, project);
505 }
506
507 collapse_unavailable_versions(&mut tree);
508 collapse_redundant_depends_on_no_versions(&mut tree);
509
510 simplify_derivation_tree_ranges(
511 &mut tree,
512 &self.available_versions,
513 &self.selector,
514 &self.env,
515 );
516
517 collapse_redundant_no_versions(&mut tree);
519
520 while collapse_redundant_no_versions_tree(&mut tree) {
521 }
523
524 if should_display_tree {
525 display_tree(&tree, "Resolver derivation tree after reduction");
526 }
527
528 let report = DefaultStringReporter::report_with_formatter(&tree, &formatter);
529 write!(f, "{report}")?;
530
531 let mut additional_hints = IndexSet::default();
533 formatter.generate_hints(
534 &tree,
535 &self.index,
536 &self.selector,
537 &self.index_locations,
538 &self.index_capabilities,
539 &self.available_indexes,
540 &self.unavailable_packages,
541 &self.incomplete_packages,
542 &self.fork_urls,
543 &self.fork_indexes,
544 &self.env,
545 self.tags.as_ref(),
546 &self.workspace_members,
547 &self.options,
548 &mut additional_hints,
549 );
550 for hint in additional_hints {
551 write!(f, "\n\n{hint}")?;
552 }
553
554 self.hint_disjoint_targets(f)?;
555
556 Ok(())
557 }
558}
559
560#[allow(clippy::print_stderr)]
561fn display_tree(
562 error: &DerivationTree<PubGrubPackage, Range<Version>, UnavailableReason>,
563 name: &str,
564) {
565 let mut lines = Vec::new();
566 display_tree_inner(error, &mut lines, 0);
567 lines.reverse();
568
569 if std::env::var_os(EnvVars::UV_INTERNAL__SHOW_DERIVATION_TREE).is_some() {
570 eprintln!("{name}\n{}", lines.join("\n"));
571 } else {
572 trace!("{name}\n{}", lines.join("\n"));
573 }
574}
575
576fn display_tree_inner(
577 error: &DerivationTree<PubGrubPackage, Range<Version>, UnavailableReason>,
578 lines: &mut Vec<String>,
579 depth: usize,
580) {
581 let prefix = " ".repeat(depth);
582 match error {
583 DerivationTree::Derived(derived) => {
584 display_tree_inner(&derived.cause1, lines, depth + 1);
585 display_tree_inner(&derived.cause2, lines, depth + 1);
586 for (package, term) in &derived.terms {
587 match term {
588 Term::Positive(versions) => {
589 lines.push(format!("{prefix}term {package}{versions}"));
590 }
591 Term::Negative(versions) => {
592 lines.push(format!("{prefix}term not {package}{versions}"));
593 }
594 }
595 }
596 }
597 DerivationTree::External(external) => match external {
598 External::FromDependencyOf(package, version, dependency, dependency_version) => {
599 lines.push(format!(
600 "{prefix}{package}{version} depends on {dependency}{dependency_version}"
601 ));
602 }
603 External::Custom(package, versions, reason) => match reason {
604 UnavailableReason::Package(_) => {
605 lines.push(format!("{prefix}{package} {reason}"));
606 }
607 UnavailableReason::Version(_) => {
608 lines.push(format!("{prefix}{package}{versions} {reason}"));
609 }
610 },
611 External::NoVersions(package, versions) => {
612 lines.push(format!("{prefix}no versions of {package}{versions}"));
613 }
614 External::NotRoot(package, versions) => {
615 lines.push(format!("{prefix}not root {package}{versions}"));
616 }
617 },
618 }
619}
620
621fn collapse_redundant_no_versions(
622 tree: &mut DerivationTree<PubGrubPackage, Range<Version>, UnavailableReason>,
623) {
624 match tree {
625 DerivationTree::External(_) => {}
626 DerivationTree::Derived(derived) => {
627 match (
628 Arc::make_mut(&mut derived.cause1),
629 Arc::make_mut(&mut derived.cause2),
630 ) {
631 (
633 DerivationTree::External(External::NoVersions(package, versions)),
634 ref mut other,
635 )
636 | (
637 ref mut other,
638 DerivationTree::External(External::NoVersions(package, versions)),
639 ) => {
640 collapse_redundant_no_versions(other);
642
643 let package_terms = if let DerivationTree::Derived(derived) = other {
645 derived.terms.get(package)
646 } else {
647 derived.terms.get(package)
648 };
649
650 let Some(Term::Positive(term)) = package_terms else {
651 return;
652 };
653
654 let versions = versions.complement();
655
656 if versions.as_singleton().is_some() {
659 return;
660 }
661
662 if *term != Range::full() && *term != versions {
666 return;
667 }
668
669 *tree = other.clone();
670 }
671 _ => {
673 collapse_redundant_no_versions(Arc::make_mut(&mut derived.cause1));
674 collapse_redundant_no_versions(Arc::make_mut(&mut derived.cause2));
675 }
676 }
677 }
678 }
679}
680
681fn collapse_redundant_no_versions_tree(
716 tree: &mut DerivationTree<PubGrubPackage, Range<Version>, UnavailableReason>,
717) -> bool {
718 match tree {
719 DerivationTree::External(_) => false,
720 DerivationTree::Derived(derived) => {
721 match (
722 Arc::make_mut(&mut derived.cause1),
723 Arc::make_mut(&mut derived.cause2),
724 ) {
725 (
727 DerivationTree::External(External::NoVersions(package, versions)),
728 DerivationTree::External(External::NoVersions(other_package, other_versions)),
729 ) if package == other_package => {
730 let Some(Term::Positive(term)) = derived.terms.get(package) else {
732 return false;
733 };
734
735 if versions.subset_of(term) && other_versions.subset_of(term) {
737 *tree = DerivationTree::External(External::NoVersions(
738 package.clone(),
739 term.clone(),
740 ));
741 return true;
742 }
743
744 false
745 }
746 _ => {
748 collapse_redundant_no_versions_tree(Arc::make_mut(&mut derived.cause1))
749 || collapse_redundant_no_versions_tree(Arc::make_mut(&mut derived.cause2))
750 }
751 }
752 }
753 }
754}
755
756fn collapse_no_versions_of_workspace_members(
759 tree: &mut DerivationTree<PubGrubPackage, Range<Version>, UnavailableReason>,
760 workspace_members: &BTreeSet<PackageName>,
761) {
762 match tree {
763 DerivationTree::External(_) => {}
764 DerivationTree::Derived(derived) => {
765 match (
766 Arc::make_mut(&mut derived.cause1),
767 Arc::make_mut(&mut derived.cause2),
768 ) {
769 (DerivationTree::External(External::NoVersions(package, _)), ref mut other)
771 | (ref mut other, DerivationTree::External(External::NoVersions(package, _))) => {
772 collapse_no_versions_of_workspace_members(other, workspace_members);
774
775 let (PubGrubPackageInner::Package { name, .. }
777 | PubGrubPackageInner::Extra { name, .. }
778 | PubGrubPackageInner::Group { name, .. }) = &**package
779 else {
780 return;
781 };
782 if !workspace_members.contains(name) {
783 return;
784 }
785
786 *tree = other.clone();
788 }
789 _ => {
791 collapse_no_versions_of_workspace_members(
792 Arc::make_mut(&mut derived.cause1),
793 workspace_members,
794 );
795 collapse_no_versions_of_workspace_members(
796 Arc::make_mut(&mut derived.cause2),
797 workspace_members,
798 );
799 }
800 }
801 }
802 }
803}
804
805fn collapse_redundant_depends_on_no_versions(
827 tree: &mut DerivationTree<PubGrubPackage, Range<Version>, UnavailableReason>,
828) {
829 match tree {
830 DerivationTree::External(_) => {}
831 DerivationTree::Derived(derived) => {
832 match (
834 Arc::make_mut(&mut derived.cause1),
835 Arc::make_mut(&mut derived.cause2),
836 ) {
837 (
838 DerivationTree::External(External::FromDependencyOf(package, versions, _, _)),
839 ref mut other,
840 )
841 | (
842 ref mut other,
843 DerivationTree::External(External::FromDependencyOf(package, versions, _, _)),
844 ) => {
845 collapse_redundant_depends_on_no_versions_inner(other, package, versions);
847 }
848 _ => {
850 collapse_redundant_depends_on_no_versions(Arc::make_mut(&mut derived.cause1));
851 collapse_redundant_depends_on_no_versions(Arc::make_mut(&mut derived.cause2));
852 }
853 }
854 }
855 }
856}
857
858fn collapse_redundant_depends_on_no_versions_inner(
860 tree: &mut DerivationTree<PubGrubPackage, Range<Version>, UnavailableReason>,
861 package: &PubGrubPackage,
862 versions: &Range<Version>,
863) {
864 match tree {
865 DerivationTree::External(_) => {}
866 DerivationTree::Derived(derived) => {
867 match (&*derived.cause1, &*derived.cause2) {
869 (
870 DerivationTree::External(External::NoVersions(no_versions_package, _)),
871 dependency_clause @ DerivationTree::External(External::FromDependencyOf(
872 _,
873 _,
874 dependency_package,
875 dependency_versions,
876 )),
877 )
878 | (
879 dependency_clause @ DerivationTree::External(External::FromDependencyOf(
880 _,
881 _,
882 dependency_package,
883 dependency_versions,
884 )),
885 DerivationTree::External(External::NoVersions(no_versions_package, _)),
886 )
887 if no_versions_package == dependency_package
890 && package == no_versions_package
891 && versions.subset_of(dependency_versions) =>
893 {
894 *tree = dependency_clause.clone();
897
898 }
900 _ => {
902 collapse_redundant_depends_on_no_versions(Arc::make_mut(&mut derived.cause1));
903 collapse_redundant_depends_on_no_versions(Arc::make_mut(&mut derived.cause2));
904 }
905 }
906 }
907 }
908}
909
910fn simplify_derivation_tree_markers(
919 python_requirement: &PythonRequirement,
920 tree: &mut DerivationTree<PubGrubPackage, Range<Version>, UnavailableReason>,
921) {
922 match tree {
923 DerivationTree::External(External::NotRoot(pkg, _)) => {
924 pkg.simplify_markers(python_requirement);
925 }
926 DerivationTree::External(External::NoVersions(pkg, _)) => {
927 pkg.simplify_markers(python_requirement);
928 }
929 DerivationTree::External(External::FromDependencyOf(pkg1, _, pkg2, _)) => {
930 pkg1.simplify_markers(python_requirement);
931 pkg2.simplify_markers(python_requirement);
932 }
933 DerivationTree::External(External::Custom(pkg, _, _)) => {
934 pkg.simplify_markers(python_requirement);
935 }
936 DerivationTree::Derived(derived) => {
937 derived.terms = std::mem::take(&mut derived.terms)
938 .into_iter()
939 .map(|(mut pkg, term)| {
940 pkg.simplify_markers(python_requirement);
941 (pkg, term)
942 })
943 .collect();
944 simplify_derivation_tree_markers(
945 python_requirement,
946 Arc::make_mut(&mut derived.cause1),
947 );
948 simplify_derivation_tree_markers(
949 python_requirement,
950 Arc::make_mut(&mut derived.cause2),
951 );
952 }
953 }
954}
955
956fn collapse_unavailable_versions(
960 tree: &mut DerivationTree<PubGrubPackage, Range<Version>, UnavailableReason>,
961) {
962 match tree {
963 DerivationTree::External(_) => {}
964 DerivationTree::Derived(derived) => {
965 match (
966 Arc::make_mut(&mut derived.cause1),
967 Arc::make_mut(&mut derived.cause2),
968 ) {
969 (
971 DerivationTree::External(External::Custom(package, versions, reason)),
972 ref mut other,
973 )
974 | (
975 ref mut other,
976 DerivationTree::External(External::Custom(package, versions, reason)),
977 ) => {
978 collapse_unavailable_versions(other);
980
981 let DerivationTree::Derived(Derived {
983 terms,
984 shared_id,
985 cause1,
986 cause2,
987 }) = other
988 else {
989 return;
990 };
991
992 match (&**cause1, &**cause2) {
994 (
997 _,
998 DerivationTree::External(External::Custom(
999 other_package,
1000 other_versions,
1001 other_reason,
1002 )),
1003 ) => {
1004 if package == other_package && reason == other_reason {
1006 let versions = other_versions.union(versions);
1008 let mut terms = terms.clone();
1009 if let Some(Term::Positive(range)) = terms.get_mut(package) {
1010 *range = versions.clone();
1011 }
1012 *tree = DerivationTree::Derived(Derived {
1013 terms,
1014 shared_id: *shared_id,
1015 cause1: cause1.clone(),
1016 cause2: Arc::new(DerivationTree::External(External::Custom(
1017 package.clone(),
1018 versions,
1019 reason.clone(),
1020 ))),
1021 });
1022 }
1023 }
1024 (
1025 DerivationTree::External(External::Custom(
1026 other_package,
1027 other_versions,
1028 other_reason,
1029 )),
1030 _,
1031 ) => {
1032 if package == other_package && reason == other_reason {
1034 let versions = other_versions.union(versions);
1036 let mut terms = terms.clone();
1037 if let Some(Term::Positive(range)) = terms.get_mut(package) {
1038 *range = versions.clone();
1039 }
1040 *tree = DerivationTree::Derived(Derived {
1041 terms,
1042 shared_id: *shared_id,
1043 cause1: Arc::new(DerivationTree::External(External::Custom(
1044 package.clone(),
1045 versions,
1046 reason.clone(),
1047 ))),
1048 cause2: cause2.clone(),
1049 });
1050 }
1051 }
1052 _ => {}
1053 }
1054 }
1055 _ => {
1057 collapse_unavailable_versions(Arc::make_mut(&mut derived.cause1));
1058 collapse_unavailable_versions(Arc::make_mut(&mut derived.cause2));
1059 }
1060 }
1061 }
1062 }
1063}
1064
1065fn drop_root_dependency_on_project(
1073 tree: &mut DerivationTree<PubGrubPackage, Range<Version>, UnavailableReason>,
1074 project: &PackageName,
1075) {
1076 match tree {
1077 DerivationTree::External(_) => {}
1078 DerivationTree::Derived(derived) => {
1079 match (
1080 Arc::make_mut(&mut derived.cause1),
1081 Arc::make_mut(&mut derived.cause2),
1082 ) {
1083 (
1085 DerivationTree::External(External::FromDependencyOf(package, _, dependency, _)),
1086 ref mut other,
1087 )
1088 | (
1089 ref mut other,
1090 DerivationTree::External(External::FromDependencyOf(package, _, dependency, _)),
1091 ) => {
1092 if !matches!(&**package, PubGrubPackageInner::Root(_)) {
1094 return;
1095 }
1096
1097 let PubGrubPackageInner::Package { name, .. } = &**dependency else {
1099 return;
1100 };
1101 if name != project {
1102 return;
1103 }
1104
1105 drop_root_dependency_on_project(other, project);
1107
1108 *tree = other.clone();
1110 }
1111 _ => {
1113 drop_root_dependency_on_project(Arc::make_mut(&mut derived.cause1), project);
1114 drop_root_dependency_on_project(Arc::make_mut(&mut derived.cause2), project);
1115 }
1116 }
1117 }
1118 }
1119}
1120
1121#[derive(Debug)]
1123pub struct SentinelRange<'range>(&'range Range<Version>);
1124
1125impl<'range> From<&'range Range<Version>> for SentinelRange<'range> {
1126 fn from(range: &'range Range<Version>) -> Self {
1127 Self(range)
1128 }
1129}
1130
1131impl SentinelRange<'_> {
1132 pub fn is_sentinel(&self) -> bool {
1134 self.0.iter().all(|(lower, upper)| {
1135 let (Bound::Included(lower), Bound::Excluded(upper)) = (lower, upper) else {
1136 return false;
1137 };
1138 if !lower.local().is_empty() {
1139 return false;
1140 }
1141 if upper.local() != LocalVersionSlice::Max {
1142 return false;
1143 }
1144 *lower == upper.clone().without_local()
1145 })
1146 }
1147
1148 pub fn is_complement(&self) -> bool {
1151 self.0.iter().all(|(lower, upper)| {
1152 let (Bound::Excluded(lower), Bound::Excluded(upper)) = (lower, upper) else {
1153 return false;
1154 };
1155 if !lower.local().is_empty() {
1156 return false;
1157 }
1158 if upper.local() != LocalVersionSlice::Max {
1159 return false;
1160 }
1161 *lower == upper.clone().without_local()
1162 })
1163 }
1164
1165 pub fn strip(&self) -> Ranges<Version> {
1167 self.0
1168 .iter()
1169 .map(|(lower, upper)| Self::strip_sentinel(lower.clone(), upper.clone()))
1170 .collect()
1171 }
1172
1173 fn strip_sentinel(
1175 mut lower: Bound<Version>,
1176 mut upper: Bound<Version>,
1177 ) -> (Bound<Version>, Bound<Version>) {
1178 match (&lower, &upper) {
1179 (Bound::Unbounded, Bound::Unbounded) => {}
1180 (Bound::Unbounded, Bound::Included(v)) => {
1181 if v.local() == LocalVersionSlice::Max {
1183 upper = Bound::Included(v.clone().without_local());
1184 }
1185 }
1186 (Bound::Unbounded, Bound::Excluded(v)) => {
1187 if v.local() == LocalVersionSlice::Max {
1189 upper = Bound::Excluded(v.clone().without_local());
1190 }
1191 }
1192 (Bound::Included(v), Bound::Unbounded) => {
1193 if v.local() == LocalVersionSlice::Max {
1195 lower = Bound::Excluded(v.clone().without_local());
1196 }
1197 }
1198 (Bound::Included(v), Bound::Included(b)) => {
1199 if v.local() == LocalVersionSlice::Max {
1201 lower = Bound::Excluded(v.clone().without_local());
1202 }
1203 if b.local() == LocalVersionSlice::Max {
1205 upper = Bound::Included(b.clone().without_local());
1206 }
1207 }
1208 (Bound::Included(v), Bound::Excluded(b)) => {
1209 if v.local() == LocalVersionSlice::Max {
1211 lower = Bound::Excluded(v.clone().without_local());
1212 }
1213 if b.local() == LocalVersionSlice::Max {
1215 upper = Bound::Included(b.clone().without_local());
1216 }
1217 }
1218 (Bound::Excluded(v), Bound::Unbounded) => {
1219 if v.local() == LocalVersionSlice::Max {
1221 lower = Bound::Excluded(v.clone().without_local());
1222 }
1223 }
1224 (Bound::Excluded(v), Bound::Included(b)) => {
1225 if v.local() == LocalVersionSlice::Max {
1227 lower = Bound::Excluded(v.clone().without_local());
1228 }
1229 if b.local() == LocalVersionSlice::Max {
1231 upper = Bound::Included(b.clone().without_local());
1232 }
1233 }
1234 (Bound::Excluded(v), Bound::Excluded(b)) => {
1235 if v.local() == LocalVersionSlice::Max {
1237 lower = Bound::Excluded(v.clone().without_local());
1238 }
1239 if b.local() == LocalVersionSlice::Max {
1241 upper = Bound::Excluded(b.clone().without_local());
1242 }
1243 }
1244 }
1245 (lower, upper)
1246 }
1247}
1248
1249#[derive(Debug, Clone, PartialEq, Eq)]
1251pub(crate) struct PrefixMatch<'a> {
1252 version: &'a Version,
1253}
1254
1255impl<'a> PrefixMatch<'a> {
1256 pub(crate) fn from_range(lower: &'a Bound<Version>, upper: &'a Bound<Version>) -> Option<Self> {
1261 let Bound::Included(lower) = lower else {
1262 return None;
1263 };
1264 let Bound::Excluded(upper) = upper else {
1265 return None;
1266 };
1267 if lower.is_pre() || lower.is_post() || lower.is_local() {
1268 return None;
1269 }
1270 if upper.is_pre() || upper.is_post() || upper.is_local() {
1271 return None;
1272 }
1273 if lower.dev() != Some(0) {
1274 return None;
1275 }
1276 if upper.dev() != Some(0) {
1277 return None;
1278 }
1279 if lower.release().len() != upper.release().len() {
1280 return None;
1281 }
1282
1283 let num_segments = lower.release().len();
1285 for (i, (lower, upper)) in lower
1286 .release()
1287 .iter()
1288 .zip(upper.release().iter())
1289 .enumerate()
1290 {
1291 if i == num_segments - 1 {
1292 if lower + 1 != *upper {
1293 return None;
1294 }
1295 } else {
1296 if lower != upper {
1297 return None;
1298 }
1299 }
1300 }
1301
1302 Some(PrefixMatch { version: lower })
1303 }
1304}
1305
1306impl std::fmt::Display for PrefixMatch<'_> {
1307 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1308 write!(f, "=={}.*", self.version.only_release())
1309 }
1310}
1311
1312#[derive(Debug)]
1313pub struct NoSolutionHeader {
1314 env: ResolverEnvironment,
1316 context: Option<&'static str>,
1318}
1319
1320impl NoSolutionHeader {
1321 pub fn new(env: ResolverEnvironment) -> Self {
1323 Self { env, context: None }
1324 }
1325
1326 #[must_use]
1328 pub fn with_context(mut self, context: &'static str) -> Self {
1329 self.context = Some(context);
1330 self
1331 }
1332}
1333
1334impl std::fmt::Display for NoSolutionHeader {
1335 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1336 match (self.context, self.env.end_user_fork_display()) {
1337 (None, None) => write!(f, "No solution found when resolving dependencies:"),
1338 (Some(context), None) => write!(
1339 f,
1340 "No solution found when resolving {context} dependencies:"
1341 ),
1342 (None, Some(split)) => write!(
1343 f,
1344 "No solution found when resolving dependencies for {split}:"
1345 ),
1346 (Some(context), Some(split)) => write!(
1347 f,
1348 "No solution found when resolving {context} dependencies for {split}:"
1349 ),
1350 }
1351 }
1352}
1353
1354fn simplify_derivation_tree_ranges(
1357 tree: &mut DerivationTree<PubGrubPackage, Range<Version>, UnavailableReason>,
1358 available_versions: &FxHashMap<PackageName, BTreeSet<Version>>,
1359 candidate_selector: &CandidateSelector,
1360 resolver_environment: &ResolverEnvironment,
1361) {
1362 match tree {
1363 DerivationTree::External(external) => match external {
1364 External::FromDependencyOf(package1, versions1, package2, versions2) => {
1365 if let Some(simplified) = simplify_range(
1366 versions1,
1367 package1,
1368 available_versions,
1369 candidate_selector,
1370 resolver_environment,
1371 ) {
1372 *versions1 = simplified;
1373 }
1374 if let Some(simplified) = simplify_range(
1375 versions2,
1376 package2,
1377 available_versions,
1378 candidate_selector,
1379 resolver_environment,
1380 ) {
1381 *versions2 = simplified;
1382 }
1383 }
1384 External::NoVersions(package, versions) => {
1385 if let Some(simplified) = simplify_range(
1386 versions,
1387 package,
1388 available_versions,
1389 candidate_selector,
1390 resolver_environment,
1391 ) {
1392 *versions = simplified;
1393 }
1394 }
1395 External::Custom(package, versions, _) => {
1396 if let Some(simplified) = simplify_range(
1397 versions,
1398 package,
1399 available_versions,
1400 candidate_selector,
1401 resolver_environment,
1402 ) {
1403 *versions = simplified;
1404 }
1405 }
1406 External::NotRoot(..) => (),
1407 },
1408 DerivationTree::Derived(derived) => {
1409 simplify_derivation_tree_ranges(
1411 Arc::make_mut(&mut derived.cause1),
1412 available_versions,
1413 candidate_selector,
1414 resolver_environment,
1415 );
1416 simplify_derivation_tree_ranges(
1417 Arc::make_mut(&mut derived.cause2),
1418 available_versions,
1419 candidate_selector,
1420 resolver_environment,
1421 );
1422
1423 derived.terms = std::mem::take(&mut derived.terms)
1425 .into_iter()
1426 .map(|(pkg, term)| {
1427 let term = match term {
1428 Term::Positive(versions) => Term::Positive(
1429 simplify_range(
1430 &versions,
1431 &pkg,
1432 available_versions,
1433 candidate_selector,
1434 resolver_environment,
1435 )
1436 .unwrap_or(versions),
1437 ),
1438 Term::Negative(versions) => Term::Negative(
1439 simplify_range(
1440 &versions,
1441 &pkg,
1442 available_versions,
1443 candidate_selector,
1444 resolver_environment,
1445 )
1446 .unwrap_or(versions),
1447 ),
1448 };
1449 (pkg, term)
1450 })
1451 .collect();
1452 }
1453 }
1454}
1455
1456fn simplify_range(
1460 range: &Range<Version>,
1461 package: &PubGrubPackage,
1462 available_versions: &FxHashMap<PackageName, BTreeSet<Version>>,
1463 candidate_selector: &CandidateSelector,
1464 resolver_environment: &ResolverEnvironment,
1465) -> Option<Range<Version>> {
1466 let name = package.name()?;
1468 let versions = available_versions.get(name)?;
1469
1470 if range == &Range::full() {
1472 return None;
1473 }
1474
1475 if let Some(version) = versions.iter().next() {
1477 if versions.len() == 1 && range.contains(version) {
1478 return Some(Range::singleton(version.clone()));
1479 }
1480 }
1481
1482 let prereleases_not_allowed = candidate_selector
1484 .prerelease_strategy()
1485 .allows(name, resolver_environment)
1486 != AllowPrerelease::Yes;
1487
1488 let any_prerelease = range.iter().any(|(start, end)| {
1489 let is_pre1 = match start {
1490 Bound::Included(version) => version.any_prerelease(),
1491 Bound::Excluded(version) => version.any_prerelease(),
1492 Bound::Unbounded => false,
1493 };
1494 let is_pre2 = match end {
1495 Bound::Included(version) => version.any_prerelease(),
1496 Bound::Excluded(version) => version.any_prerelease(),
1497 Bound::Unbounded => false,
1498 };
1499 is_pre1 || is_pre2
1500 });
1501
1502 Some(range.simplify(versions.iter().filter(|version| {
1504 if any_prerelease {
1506 return true;
1507 }
1508
1509 if prereleases_not_allowed && version.any_prerelease() {
1511 return false;
1512 }
1513
1514 true
1516 })))
1517}