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