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 available_versions: FxHashMap<PackageName, BTreeSet<Version>>,
166 available_indexes: FxHashMap<PackageName, BTreeSet<IndexUrl>>,
167 selector: CandidateSelector,
168 python_requirement: PythonRequirement,
169 index_locations: IndexLocations,
170 index_capabilities: IndexCapabilities,
171 unavailable_packages: FxHashMap<PackageName, UnavailablePackage>,
172 incomplete_packages: FxHashMap<PackageName, BTreeMap<Version, MetadataUnavailable>>,
173 fork_urls: ForkUrls,
174 fork_indexes: ForkIndexes,
175 env: ResolverEnvironment,
176 current_environment: MarkerEnvironment,
177 tags: Option<Tags>,
178 workspace_members: BTreeSet<PackageName>,
179 options: Options,
180}
181
182impl NoSolutionError {
183 pub(crate) fn new(
185 error: pubgrub::NoSolutionError<UvDependencyProvider>,
186 index: InMemoryIndex,
187 available_versions: FxHashMap<PackageName, BTreeSet<Version>>,
188 available_indexes: FxHashMap<PackageName, BTreeSet<IndexUrl>>,
189 selector: CandidateSelector,
190 python_requirement: PythonRequirement,
191 index_locations: IndexLocations,
192 index_capabilities: IndexCapabilities,
193 unavailable_packages: FxHashMap<PackageName, UnavailablePackage>,
194 incomplete_packages: FxHashMap<PackageName, BTreeMap<Version, MetadataUnavailable>>,
195 fork_urls: ForkUrls,
196 fork_indexes: ForkIndexes,
197 env: ResolverEnvironment,
198 current_environment: MarkerEnvironment,
199 tags: Option<Tags>,
200 workspace_members: BTreeSet<PackageName>,
201 options: Options,
202 ) -> Self {
203 Self {
204 error,
205 index,
206 available_versions,
207 available_indexes,
208 selector,
209 python_requirement,
210 index_locations,
211 index_capabilities,
212 unavailable_packages,
213 incomplete_packages,
214 fork_urls,
215 fork_indexes,
216 env,
217 current_environment,
218 tags,
219 workspace_members,
220 options,
221 }
222 }
223
224 pub(crate) fn collapse_proxies(derivation_tree: ErrorTree) -> ErrorTree {
227 fn collapse(derivation_tree: ErrorTree) -> Option<ErrorTree> {
228 match derivation_tree {
229 DerivationTree::Derived(derived) => {
230 match (&*derived.cause1, &*derived.cause2) {
231 (
232 DerivationTree::External(External::FromDependencyOf(package1, ..)),
233 DerivationTree::External(External::FromDependencyOf(package2, ..)),
234 ) if package1.is_proxy() && package2.is_proxy() => None,
235 (
236 DerivationTree::External(External::FromDependencyOf(package, ..)),
237 cause,
238 ) if package.is_proxy() => collapse(cause.clone()),
239 (
240 cause,
241 DerivationTree::External(External::FromDependencyOf(package, ..)),
242 ) if package.is_proxy() => collapse(cause.clone()),
243 (cause1, cause2) => {
244 let cause1 = collapse(cause1.clone());
245 let cause2 = collapse(cause2.clone());
246 match (cause1, cause2) {
247 (Some(cause1), Some(cause2)) => {
248 Some(DerivationTree::Derived(Derived {
249 cause1: Arc::new(cause1),
250 cause2: Arc::new(cause2),
251 ..derived
252 }))
253 }
254 (Some(cause), None) | (None, Some(cause)) => Some(cause),
255 _ => None,
256 }
257 }
258 }
259 }
260 DerivationTree::External(_) => Some(derivation_tree),
261 }
262 }
263
264 collapse(derivation_tree)
265 .expect("derivation tree should contain at least one external term")
266 }
267
268 pub(crate) fn collapse_local_version_segments(derivation_tree: ErrorTree) -> ErrorTree {
274 fn strip(derivation_tree: ErrorTree) -> Option<ErrorTree> {
275 match derivation_tree {
276 DerivationTree::External(External::NotRoot(_, _)) => Some(derivation_tree),
277 DerivationTree::External(External::NoVersions(package, versions)) => {
278 if SentinelRange::from(&versions).is_complement() {
279 return None;
280 }
281
282 let versions = SentinelRange::from(&versions).strip();
283 Some(DerivationTree::External(External::NoVersions(
284 package, versions,
285 )))
286 }
287 DerivationTree::External(External::FromDependencyOf(
288 package1,
289 versions1,
290 package2,
291 versions2,
292 )) => {
293 let versions1 = SentinelRange::from(&versions1).strip();
294 let versions2 = SentinelRange::from(&versions2).strip();
295 Some(DerivationTree::External(External::FromDependencyOf(
296 package1, versions1, package2, versions2,
297 )))
298 }
299 DerivationTree::External(External::Custom(package, versions, reason)) => {
300 let versions = SentinelRange::from(&versions).strip();
301 Some(DerivationTree::External(External::Custom(
302 package, versions, reason,
303 )))
304 }
305 DerivationTree::Derived(mut derived) => {
306 let cause1 = strip((*derived.cause1).clone());
307 let cause2 = strip((*derived.cause2).clone());
308 match (cause1, cause2) {
309 (Some(cause1), Some(cause2)) => Some(DerivationTree::Derived(Derived {
310 cause1: Arc::new(cause1),
311 cause2: Arc::new(cause2),
312 terms: std::mem::take(&mut derived.terms)
313 .into_iter()
314 .map(|(pkg, term)| {
315 let term = match term {
316 Term::Positive(versions) => {
317 Term::Positive(SentinelRange::from(&versions).strip())
318 }
319 Term::Negative(versions) => {
320 Term::Negative(SentinelRange::from(&versions).strip())
321 }
322 };
323 (pkg, term)
324 })
325 .collect(),
326 shared_id: derived.shared_id,
327 })),
328 (Some(cause), None) | (None, Some(cause)) => Some(cause),
329 _ => None,
330 }
331 }
332 }
333 }
334
335 strip(derivation_tree).expect("derivation tree should contain at least one term")
336 }
337
338 pub fn find_requires_python(&self) -> LowerBound {
340 fn find(derivation_tree: &ErrorTree, minimum: &mut LowerBound) {
341 match derivation_tree {
342 DerivationTree::Derived(derived) => {
343 find(derived.cause1.as_ref(), minimum);
344 find(derived.cause2.as_ref(), minimum);
345 }
346 DerivationTree::External(External::FromDependencyOf(.., package, version)) => {
347 if let PubGrubPackageInner::Python(_) = &**package {
348 if let Some((lower, ..)) = version.bounding_range() {
349 let lower = LowerBound::new(lower.cloned());
350 if lower > *minimum {
351 *minimum = lower;
352 }
353 }
354 }
355 }
356 DerivationTree::External(_) => {}
357 }
358 }
359
360 let mut minimum = LowerBound::default();
361 find(&self.error, &mut minimum);
362 minimum
363 }
364
365 pub fn header(&self) -> NoSolutionHeader {
367 NoSolutionHeader::new(self.env.clone())
368 }
369
370 pub fn derivation_tree(&self) -> &ErrorTree {
372 &self.error
373 }
374
375 pub fn packages(&self) -> impl Iterator<Item = &PackageName> {
377 self.error
378 .packages()
379 .into_iter()
380 .filter_map(|p| p.name())
381 .unique()
382 }
383}
384
385impl std::fmt::Debug for NoSolutionError {
386 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
387 let Self {
389 error,
390 index: _,
391 available_versions,
392 available_indexes,
393 selector,
394 python_requirement,
395 index_locations,
396 index_capabilities,
397 unavailable_packages,
398 incomplete_packages,
399 fork_urls,
400 fork_indexes,
401 env,
402 current_environment,
403 tags,
404 workspace_members,
405 options,
406 } = self;
407 f.debug_struct("NoSolutionError")
408 .field("error", error)
409 .field("available_versions", available_versions)
410 .field("available_indexes", available_indexes)
411 .field("selector", selector)
412 .field("python_requirement", python_requirement)
413 .field("index_locations", index_locations)
414 .field("index_capabilities", index_capabilities)
415 .field("unavailable_packages", unavailable_packages)
416 .field("incomplete_packages", incomplete_packages)
417 .field("fork_urls", fork_urls)
418 .field("fork_indexes", fork_indexes)
419 .field("env", env)
420 .field("current_environment", current_environment)
421 .field("tags", tags)
422 .field("workspace_members", workspace_members)
423 .field("options", options)
424 .finish()
425 }
426}
427
428impl std::error::Error for NoSolutionError {}
429
430impl std::fmt::Display for NoSolutionError {
431 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
432 let formatter = PubGrubReportFormatter {
434 available_versions: &self.available_versions,
435 python_requirement: &self.python_requirement,
436 workspace_members: &self.workspace_members,
437 tags: self.tags.as_ref(),
438 };
439
440 let mut tree = self.error.clone();
442 simplify_derivation_tree_markers(&self.python_requirement, &mut tree);
443 let should_display_tree = std::env::var_os(EnvVars::UV_INTERNAL__SHOW_DERIVATION_TREE)
444 .is_some()
445 || tracing::enabled!(tracing::Level::TRACE);
446
447 if should_display_tree {
448 display_tree(&tree, "Resolver derivation tree before reduction");
449 }
450
451 collapse_no_versions_of_workspace_members(&mut tree, &self.workspace_members);
452
453 if self.workspace_members.len() == 1 {
454 let project = self.workspace_members.iter().next().unwrap();
455 drop_root_dependency_on_project(&mut tree, project);
456 }
457
458 collapse_unavailable_versions(&mut tree);
459 collapse_redundant_depends_on_no_versions(&mut tree);
460
461 simplify_derivation_tree_ranges(
462 &mut tree,
463 &self.available_versions,
464 &self.selector,
465 &self.env,
466 );
467
468 collapse_redundant_no_versions(&mut tree);
470
471 while collapse_redundant_no_versions_tree(&mut tree) {
472 }
474
475 if should_display_tree {
476 display_tree(&tree, "Resolver derivation tree after reduction");
477 }
478
479 let report = DefaultStringReporter::report_with_formatter(&tree, &formatter);
480 write!(f, "{report}")?;
481
482 let mut additional_hints = IndexSet::default();
484 formatter.generate_hints(
485 &tree,
486 &self.index,
487 &self.selector,
488 &self.index_locations,
489 &self.index_capabilities,
490 &self.available_indexes,
491 &self.unavailable_packages,
492 &self.incomplete_packages,
493 &self.fork_urls,
494 &self.fork_indexes,
495 &self.env,
496 &self.current_environment,
497 self.tags.as_ref(),
498 &self.workspace_members,
499 &self.options,
500 &mut additional_hints,
501 );
502 for hint in additional_hints {
503 write!(f, "\n\n{hint}")?;
504 }
505
506 Ok(())
507 }
508}
509
510#[expect(clippy::print_stderr)]
511fn display_tree(
512 error: &DerivationTree<PubGrubPackage, Range<Version>, UnavailableReason>,
513 name: &str,
514) {
515 let mut lines = Vec::new();
516 display_tree_inner(error, &mut lines, 0);
517 lines.reverse();
518
519 if std::env::var_os(EnvVars::UV_INTERNAL__SHOW_DERIVATION_TREE).is_some() {
520 eprintln!("{name}\n{}", lines.join("\n"));
521 } else {
522 trace!("{name}\n{}", lines.join("\n"));
523 }
524}
525
526fn display_tree_inner(
527 error: &DerivationTree<PubGrubPackage, Range<Version>, UnavailableReason>,
528 lines: &mut Vec<String>,
529 depth: usize,
530) {
531 let prefix = " ".repeat(depth);
532 match error {
533 DerivationTree::Derived(derived) => {
534 display_tree_inner(&derived.cause1, lines, depth + 1);
535 display_tree_inner(&derived.cause2, lines, depth + 1);
536 for (package, term) in &derived.terms {
537 match term {
538 Term::Positive(versions) => {
539 lines.push(format!("{prefix}term {package}{versions}"));
540 }
541 Term::Negative(versions) => {
542 lines.push(format!("{prefix}term not {package}{versions}"));
543 }
544 }
545 }
546 }
547 DerivationTree::External(external) => match external {
548 External::FromDependencyOf(package, version, dependency, dependency_version) => {
549 lines.push(format!(
550 "{prefix}{package}{version} depends on {dependency}{dependency_version}"
551 ));
552 }
553 External::Custom(package, versions, reason) => match reason {
554 UnavailableReason::Package(_) => {
555 lines.push(format!("{prefix}{package} {reason}"));
556 }
557 UnavailableReason::Version(_) => {
558 lines.push(format!("{prefix}{package}{versions} {reason}"));
559 }
560 },
561 External::NoVersions(package, versions) => {
562 lines.push(format!("{prefix}no versions of {package}{versions}"));
563 }
564 External::NotRoot(package, versions) => {
565 lines.push(format!("{prefix}not root {package}{versions}"));
566 }
567 },
568 }
569}
570
571fn collapse_redundant_no_versions(
572 tree: &mut DerivationTree<PubGrubPackage, Range<Version>, UnavailableReason>,
573) {
574 match tree {
575 DerivationTree::External(_) => {}
576 DerivationTree::Derived(derived) => {
577 match (
578 Arc::make_mut(&mut derived.cause1),
579 Arc::make_mut(&mut derived.cause2),
580 ) {
581 (
583 DerivationTree::External(External::NoVersions(package, versions)),
584 ref mut other,
585 )
586 | (
587 ref mut other,
588 DerivationTree::External(External::NoVersions(package, versions)),
589 ) => {
590 collapse_redundant_no_versions(other);
592
593 let package_terms = if let DerivationTree::Derived(derived) = other {
595 derived.terms.get(package)
596 } else {
597 derived.terms.get(package)
598 };
599
600 let Some(Term::Positive(term)) = package_terms else {
601 return;
602 };
603
604 let versions = versions.complement();
605
606 if versions.as_singleton().is_some() {
609 return;
610 }
611
612 if *term != Range::full() && *term != versions {
616 return;
617 }
618
619 *tree = other.clone();
620 }
621 _ => {
623 collapse_redundant_no_versions(Arc::make_mut(&mut derived.cause1));
624 collapse_redundant_no_versions(Arc::make_mut(&mut derived.cause2));
625 }
626 }
627 }
628 }
629}
630
631fn collapse_redundant_no_versions_tree(
666 tree: &mut DerivationTree<PubGrubPackage, Range<Version>, UnavailableReason>,
667) -> bool {
668 match tree {
669 DerivationTree::External(_) => false,
670 DerivationTree::Derived(derived) => {
671 match (
672 Arc::make_mut(&mut derived.cause1),
673 Arc::make_mut(&mut derived.cause2),
674 ) {
675 (
677 DerivationTree::External(External::NoVersions(package, versions)),
678 DerivationTree::External(External::NoVersions(other_package, other_versions)),
679 ) if package == other_package => {
680 let Some(Term::Positive(term)) = derived.terms.get(package) else {
682 return false;
683 };
684
685 if versions.subset_of(term) && other_versions.subset_of(term) {
687 *tree = DerivationTree::External(External::NoVersions(
688 package.clone(),
689 term.clone(),
690 ));
691 return true;
692 }
693
694 false
695 }
696 _ => {
698 collapse_redundant_no_versions_tree(Arc::make_mut(&mut derived.cause1))
699 || collapse_redundant_no_versions_tree(Arc::make_mut(&mut derived.cause2))
700 }
701 }
702 }
703 }
704}
705
706fn collapse_no_versions_of_workspace_members(
709 tree: &mut DerivationTree<PubGrubPackage, Range<Version>, UnavailableReason>,
710 workspace_members: &BTreeSet<PackageName>,
711) {
712 match tree {
713 DerivationTree::External(_) => {}
714 DerivationTree::Derived(derived) => {
715 match (
716 Arc::make_mut(&mut derived.cause1),
717 Arc::make_mut(&mut derived.cause2),
718 ) {
719 (DerivationTree::External(External::NoVersions(package, _)), ref mut other)
721 | (ref mut other, DerivationTree::External(External::NoVersions(package, _))) => {
722 collapse_no_versions_of_workspace_members(other, workspace_members);
724
725 let (PubGrubPackageInner::Package { name, .. }
727 | PubGrubPackageInner::Extra { name, .. }
728 | PubGrubPackageInner::Group { name, .. }) = &**package
729 else {
730 return;
731 };
732 if !workspace_members.contains(name) {
733 return;
734 }
735
736 *tree = other.clone();
738 }
739 _ => {
741 collapse_no_versions_of_workspace_members(
742 Arc::make_mut(&mut derived.cause1),
743 workspace_members,
744 );
745 collapse_no_versions_of_workspace_members(
746 Arc::make_mut(&mut derived.cause2),
747 workspace_members,
748 );
749 }
750 }
751 }
752 }
753}
754
755fn collapse_redundant_depends_on_no_versions(
777 tree: &mut DerivationTree<PubGrubPackage, Range<Version>, UnavailableReason>,
778) {
779 match tree {
780 DerivationTree::External(_) => {}
781 DerivationTree::Derived(derived) => {
782 match (
784 Arc::make_mut(&mut derived.cause1),
785 Arc::make_mut(&mut derived.cause2),
786 ) {
787 (
788 DerivationTree::External(External::FromDependencyOf(package, versions, _, _)),
789 ref mut other,
790 )
791 | (
792 ref mut other,
793 DerivationTree::External(External::FromDependencyOf(package, versions, _, _)),
794 ) => {
795 collapse_redundant_depends_on_no_versions_inner(other, package, versions);
797 }
798 _ => {
800 collapse_redundant_depends_on_no_versions(Arc::make_mut(&mut derived.cause1));
801 collapse_redundant_depends_on_no_versions(Arc::make_mut(&mut derived.cause2));
802 }
803 }
804 }
805 }
806}
807
808fn collapse_redundant_depends_on_no_versions_inner(
810 tree: &mut DerivationTree<PubGrubPackage, Range<Version>, UnavailableReason>,
811 package: &PubGrubPackage,
812 versions: &Range<Version>,
813) {
814 match tree {
815 DerivationTree::External(_) => {}
816 DerivationTree::Derived(derived) => {
817 match (&*derived.cause1, &*derived.cause2) {
819 (
820 DerivationTree::External(External::NoVersions(no_versions_package, _)),
821 dependency_clause @ DerivationTree::External(External::FromDependencyOf(
822 _,
823 _,
824 dependency_package,
825 dependency_versions,
826 )),
827 )
828 | (
829 dependency_clause @ DerivationTree::External(External::FromDependencyOf(
830 _,
831 _,
832 dependency_package,
833 dependency_versions,
834 )),
835 DerivationTree::External(External::NoVersions(no_versions_package, _)),
836 )
837 if no_versions_package == dependency_package
840 && package == no_versions_package
841 && versions.subset_of(dependency_versions) =>
843 {
844 *tree = dependency_clause.clone();
847
848 }
850 _ => {
852 collapse_redundant_depends_on_no_versions(Arc::make_mut(&mut derived.cause1));
853 collapse_redundant_depends_on_no_versions(Arc::make_mut(&mut derived.cause2));
854 }
855 }
856 }
857 }
858}
859
860fn simplify_derivation_tree_markers(
869 python_requirement: &PythonRequirement,
870 tree: &mut DerivationTree<PubGrubPackage, Range<Version>, UnavailableReason>,
871) {
872 match tree {
873 DerivationTree::External(External::NotRoot(pkg, _)) => {
874 pkg.simplify_markers(python_requirement);
875 }
876 DerivationTree::External(External::NoVersions(pkg, _)) => {
877 pkg.simplify_markers(python_requirement);
878 }
879 DerivationTree::External(External::FromDependencyOf(pkg1, _, pkg2, _)) => {
880 pkg1.simplify_markers(python_requirement);
881 pkg2.simplify_markers(python_requirement);
882 }
883 DerivationTree::External(External::Custom(pkg, _, _)) => {
884 pkg.simplify_markers(python_requirement);
885 }
886 DerivationTree::Derived(derived) => {
887 derived.terms = std::mem::take(&mut derived.terms)
888 .into_iter()
889 .map(|(mut pkg, term)| {
890 pkg.simplify_markers(python_requirement);
891 (pkg, term)
892 })
893 .collect();
894 simplify_derivation_tree_markers(
895 python_requirement,
896 Arc::make_mut(&mut derived.cause1),
897 );
898 simplify_derivation_tree_markers(
899 python_requirement,
900 Arc::make_mut(&mut derived.cause2),
901 );
902 }
903 }
904}
905
906fn collapse_unavailable_versions(
910 tree: &mut DerivationTree<PubGrubPackage, Range<Version>, UnavailableReason>,
911) {
912 match tree {
913 DerivationTree::External(_) => {}
914 DerivationTree::Derived(derived) => {
915 match (
916 Arc::make_mut(&mut derived.cause1),
917 Arc::make_mut(&mut derived.cause2),
918 ) {
919 (
921 DerivationTree::External(External::Custom(package, versions, reason)),
922 ref mut other,
923 )
924 | (
925 ref mut other,
926 DerivationTree::External(External::Custom(package, versions, reason)),
927 ) => {
928 collapse_unavailable_versions(other);
930
931 let DerivationTree::Derived(Derived {
933 terms,
934 shared_id,
935 cause1,
936 cause2,
937 }) = other
938 else {
939 return;
940 };
941
942 match (&**cause1, &**cause2) {
944 (
947 _,
948 DerivationTree::External(External::Custom(
949 other_package,
950 other_versions,
951 other_reason,
952 )),
953 ) => {
954 if package == other_package && reason == other_reason {
956 let versions = other_versions.union(versions);
958 let mut terms = terms.clone();
959 if let Some(Term::Positive(range)) = terms.get_mut(package) {
960 *range = versions.clone();
961 }
962 *tree = DerivationTree::Derived(Derived {
963 terms,
964 shared_id: *shared_id,
965 cause1: cause1.clone(),
966 cause2: Arc::new(DerivationTree::External(External::Custom(
967 package.clone(),
968 versions,
969 reason.clone(),
970 ))),
971 });
972 }
973 }
974 (
975 DerivationTree::External(External::Custom(
976 other_package,
977 other_versions,
978 other_reason,
979 )),
980 _,
981 ) => {
982 if package == other_package && reason == other_reason {
984 let versions = other_versions.union(versions);
986 let mut terms = terms.clone();
987 if let Some(Term::Positive(range)) = terms.get_mut(package) {
988 *range = versions.clone();
989 }
990 *tree = DerivationTree::Derived(Derived {
991 terms,
992 shared_id: *shared_id,
993 cause1: Arc::new(DerivationTree::External(External::Custom(
994 package.clone(),
995 versions,
996 reason.clone(),
997 ))),
998 cause2: cause2.clone(),
999 });
1000 }
1001 }
1002 _ => {}
1003 }
1004 }
1005 _ => {
1007 collapse_unavailable_versions(Arc::make_mut(&mut derived.cause1));
1008 collapse_unavailable_versions(Arc::make_mut(&mut derived.cause2));
1009 }
1010 }
1011 }
1012 }
1013}
1014
1015fn drop_root_dependency_on_project(
1023 tree: &mut DerivationTree<PubGrubPackage, Range<Version>, UnavailableReason>,
1024 project: &PackageName,
1025) {
1026 match tree {
1027 DerivationTree::External(_) => {}
1028 DerivationTree::Derived(derived) => {
1029 match (
1030 Arc::make_mut(&mut derived.cause1),
1031 Arc::make_mut(&mut derived.cause2),
1032 ) {
1033 (
1035 DerivationTree::External(External::FromDependencyOf(package, _, dependency, _)),
1036 ref mut other,
1037 )
1038 | (
1039 ref mut other,
1040 DerivationTree::External(External::FromDependencyOf(package, _, dependency, _)),
1041 ) => {
1042 if !matches!(&**package, PubGrubPackageInner::Root(_)) {
1044 return;
1045 }
1046
1047 let PubGrubPackageInner::Package { name, .. } = &**dependency else {
1049 return;
1050 };
1051 if name != project {
1052 return;
1053 }
1054
1055 drop_root_dependency_on_project(other, project);
1057
1058 *tree = other.clone();
1060 }
1061 _ => {
1063 drop_root_dependency_on_project(Arc::make_mut(&mut derived.cause1), project);
1064 drop_root_dependency_on_project(Arc::make_mut(&mut derived.cause2), project);
1065 }
1066 }
1067 }
1068 }
1069}
1070
1071#[derive(Debug)]
1073pub struct SentinelRange<'range>(&'range Range<Version>);
1074
1075impl<'range> From<&'range Range<Version>> for SentinelRange<'range> {
1076 fn from(range: &'range Range<Version>) -> Self {
1077 Self(range)
1078 }
1079}
1080
1081impl SentinelRange<'_> {
1082 pub fn is_sentinel(&self) -> bool {
1084 self.0.iter().all(|(lower, upper)| {
1085 let (Bound::Included(lower), Bound::Excluded(upper)) = (lower, upper) else {
1086 return false;
1087 };
1088 if !lower.local().is_empty() {
1089 return false;
1090 }
1091 if upper.local() != LocalVersionSlice::Max {
1092 return false;
1093 }
1094 *lower == upper.clone().without_local()
1095 })
1096 }
1097
1098 pub fn is_complement(&self) -> bool {
1101 self.0.iter().all(|(lower, upper)| {
1102 let (Bound::Excluded(lower), Bound::Excluded(upper)) = (lower, upper) else {
1103 return false;
1104 };
1105 if !lower.local().is_empty() {
1106 return false;
1107 }
1108 if upper.local() != LocalVersionSlice::Max {
1109 return false;
1110 }
1111 *lower == upper.clone().without_local()
1112 })
1113 }
1114
1115 pub fn strip(&self) -> Ranges<Version> {
1117 self.0
1118 .iter()
1119 .map(|(lower, upper)| Self::strip_sentinel(lower.clone(), upper.clone()))
1120 .collect()
1121 }
1122
1123 fn strip_sentinel(
1125 mut lower: Bound<Version>,
1126 mut upper: Bound<Version>,
1127 ) -> (Bound<Version>, Bound<Version>) {
1128 match (&lower, &upper) {
1129 (Bound::Unbounded, Bound::Unbounded) => {}
1130 (Bound::Unbounded, Bound::Included(v)) => {
1131 if v.local() == LocalVersionSlice::Max {
1133 upper = Bound::Included(v.clone().without_local());
1134 }
1135 }
1136 (Bound::Unbounded, Bound::Excluded(v)) => {
1137 if v.local() == LocalVersionSlice::Max {
1139 upper = Bound::Excluded(v.clone().without_local());
1140 }
1141 }
1142 (Bound::Included(v), Bound::Unbounded) => {
1143 if v.local() == LocalVersionSlice::Max {
1145 lower = Bound::Excluded(v.clone().without_local());
1146 }
1147 }
1148 (Bound::Included(v), Bound::Included(b)) => {
1149 if v.local() == LocalVersionSlice::Max {
1151 lower = Bound::Excluded(v.clone().without_local());
1152 }
1153 if b.local() == LocalVersionSlice::Max {
1155 upper = Bound::Included(b.clone().without_local());
1156 }
1157 }
1158 (Bound::Included(v), Bound::Excluded(b)) => {
1159 if v.local() == LocalVersionSlice::Max {
1161 lower = Bound::Excluded(v.clone().without_local());
1162 }
1163 if b.local() == LocalVersionSlice::Max {
1165 upper = Bound::Included(b.clone().without_local());
1166 }
1167 }
1168 (Bound::Excluded(v), Bound::Unbounded) => {
1169 if v.local() == LocalVersionSlice::Max {
1171 lower = Bound::Excluded(v.clone().without_local());
1172 }
1173 }
1174 (Bound::Excluded(v), Bound::Included(b)) => {
1175 if v.local() == LocalVersionSlice::Max {
1177 lower = Bound::Excluded(v.clone().without_local());
1178 }
1179 if b.local() == LocalVersionSlice::Max {
1181 upper = Bound::Included(b.clone().without_local());
1182 }
1183 }
1184 (Bound::Excluded(v), Bound::Excluded(b)) => {
1185 if v.local() == LocalVersionSlice::Max {
1187 lower = Bound::Excluded(v.clone().without_local());
1188 }
1189 if b.local() == LocalVersionSlice::Max {
1191 upper = Bound::Excluded(b.clone().without_local());
1192 }
1193 }
1194 }
1195 (lower, upper)
1196 }
1197}
1198
1199#[derive(Debug, Clone, PartialEq, Eq)]
1201pub(crate) struct PrefixMatch<'a> {
1202 version: &'a Version,
1203}
1204
1205impl<'a> PrefixMatch<'a> {
1206 pub(crate) fn from_range(lower: &'a Bound<Version>, upper: &'a Bound<Version>) -> Option<Self> {
1211 let Bound::Included(lower) = lower else {
1212 return None;
1213 };
1214 let Bound::Excluded(upper) = upper else {
1215 return None;
1216 };
1217 if lower.is_pre() || lower.is_post() || lower.is_local() {
1218 return None;
1219 }
1220 if upper.is_pre() || upper.is_post() || upper.is_local() {
1221 return None;
1222 }
1223 if lower.dev() != Some(0) {
1224 return None;
1225 }
1226 if upper.dev() != Some(0) {
1227 return None;
1228 }
1229 if lower.release().len() != upper.release().len() {
1230 return None;
1231 }
1232
1233 let num_segments = lower.release().len();
1235 for (i, (lower, upper)) in lower
1236 .release()
1237 .iter()
1238 .zip(upper.release().iter())
1239 .enumerate()
1240 {
1241 if i == num_segments - 1 {
1242 if lower + 1 != *upper {
1243 return None;
1244 }
1245 } else {
1246 if lower != upper {
1247 return None;
1248 }
1249 }
1250 }
1251
1252 Some(PrefixMatch { version: lower })
1253 }
1254}
1255
1256impl std::fmt::Display for PrefixMatch<'_> {
1257 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1258 write!(f, "=={}.*", self.version.only_release())
1259 }
1260}
1261
1262#[derive(Debug)]
1263pub struct NoSolutionHeader {
1264 env: ResolverEnvironment,
1266 context: Option<&'static str>,
1268}
1269
1270impl NoSolutionHeader {
1271 pub fn new(env: ResolverEnvironment) -> Self {
1273 Self { env, context: None }
1274 }
1275
1276 #[must_use]
1278 pub fn with_context(mut self, context: &'static str) -> Self {
1279 self.context = Some(context);
1280 self
1281 }
1282}
1283
1284impl std::fmt::Display for NoSolutionHeader {
1285 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1286 match (self.context, self.env.end_user_fork_display()) {
1287 (None, None) => write!(f, "No solution found when resolving dependencies:"),
1288 (Some(context), None) => write!(
1289 f,
1290 "No solution found when resolving {context} dependencies:"
1291 ),
1292 (None, Some(split)) => write!(
1293 f,
1294 "No solution found when resolving dependencies for {split}:"
1295 ),
1296 (Some(context), Some(split)) => write!(
1297 f,
1298 "No solution found when resolving {context} dependencies for {split}:"
1299 ),
1300 }
1301 }
1302}
1303
1304fn simplify_derivation_tree_ranges(
1307 tree: &mut DerivationTree<PubGrubPackage, Range<Version>, UnavailableReason>,
1308 available_versions: &FxHashMap<PackageName, BTreeSet<Version>>,
1309 candidate_selector: &CandidateSelector,
1310 resolver_environment: &ResolverEnvironment,
1311) {
1312 match tree {
1313 DerivationTree::External(external) => match external {
1314 External::FromDependencyOf(package1, versions1, package2, versions2) => {
1315 if let Some(simplified) = simplify_range(
1316 versions1,
1317 package1,
1318 available_versions,
1319 candidate_selector,
1320 resolver_environment,
1321 ) {
1322 *versions1 = simplified;
1323 }
1324 if let Some(simplified) = simplify_range(
1325 versions2,
1326 package2,
1327 available_versions,
1328 candidate_selector,
1329 resolver_environment,
1330 ) {
1331 *versions2 = simplified;
1332 }
1333 }
1334 External::NoVersions(package, versions) => {
1335 if let Some(simplified) = simplify_range(
1336 versions,
1337 package,
1338 available_versions,
1339 candidate_selector,
1340 resolver_environment,
1341 ) {
1342 *versions = simplified;
1343 }
1344 }
1345 External::Custom(package, versions, _) => {
1346 if let Some(simplified) = simplify_range(
1347 versions,
1348 package,
1349 available_versions,
1350 candidate_selector,
1351 resolver_environment,
1352 ) {
1353 *versions = simplified;
1354 }
1355 }
1356 External::NotRoot(..) => (),
1357 },
1358 DerivationTree::Derived(derived) => {
1359 simplify_derivation_tree_ranges(
1361 Arc::make_mut(&mut derived.cause1),
1362 available_versions,
1363 candidate_selector,
1364 resolver_environment,
1365 );
1366 simplify_derivation_tree_ranges(
1367 Arc::make_mut(&mut derived.cause2),
1368 available_versions,
1369 candidate_selector,
1370 resolver_environment,
1371 );
1372
1373 derived.terms = std::mem::take(&mut derived.terms)
1375 .into_iter()
1376 .map(|(pkg, term)| {
1377 let term = match term {
1378 Term::Positive(versions) => Term::Positive(
1379 simplify_range(
1380 &versions,
1381 &pkg,
1382 available_versions,
1383 candidate_selector,
1384 resolver_environment,
1385 )
1386 .unwrap_or(versions),
1387 ),
1388 Term::Negative(versions) => Term::Negative(
1389 simplify_range(
1390 &versions,
1391 &pkg,
1392 available_versions,
1393 candidate_selector,
1394 resolver_environment,
1395 )
1396 .unwrap_or(versions),
1397 ),
1398 };
1399 (pkg, term)
1400 })
1401 .collect();
1402 }
1403 }
1404}
1405
1406fn simplify_range(
1410 range: &Range<Version>,
1411 package: &PubGrubPackage,
1412 available_versions: &FxHashMap<PackageName, BTreeSet<Version>>,
1413 candidate_selector: &CandidateSelector,
1414 resolver_environment: &ResolverEnvironment,
1415) -> Option<Range<Version>> {
1416 let name = package.name()?;
1418 let versions = available_versions.get(name)?;
1419
1420 if range == &Range::full() {
1422 return None;
1423 }
1424
1425 if let Some(version) = versions.iter().next() {
1427 if versions.len() == 1 && range.contains(version) {
1428 return Some(Range::singleton(version.clone()));
1429 }
1430 }
1431
1432 let prereleases_not_allowed = candidate_selector
1434 .prerelease_strategy()
1435 .allows(name, resolver_environment)
1436 != AllowPrerelease::Yes;
1437
1438 let any_prerelease = range.iter().any(|(start, end)| {
1439 let is_pre1 = match start {
1440 Bound::Included(version) => version.any_prerelease(),
1441 Bound::Excluded(version) => version.any_prerelease(),
1442 Bound::Unbounded => false,
1443 };
1444 let is_pre2 = match end {
1445 Bound::Included(version) => version.any_prerelease(),
1446 Bound::Excluded(version) => version.any_prerelease(),
1447 Bound::Unbounded => false,
1448 };
1449 is_pre1 || is_pre2
1450 });
1451
1452 Some(range.simplify(versions.iter().filter(|version| {
1454 if any_prerelease {
1456 return true;
1457 }
1458
1459 if prereleases_not_allowed && version.any_prerelease() {
1461 return false;
1462 }
1463
1464 true
1466 })))
1467}