diff --git a/crates/uv-resolver/src/error.rs b/crates/uv-resolver/src/error.rs index 513b99731ea5..32436a2cec67 100644 --- a/crates/uv-resolver/src/error.rs +++ b/crates/uv-resolver/src/error.rs @@ -125,6 +125,7 @@ pub struct NoSolutionError { incomplete_packages: FxHashMap>, fork_urls: ForkUrls, markers: ResolverMarkers, + workspace_members: BTreeSet, } impl NoSolutionError { @@ -139,6 +140,7 @@ impl NoSolutionError { incomplete_packages: FxHashMap>, fork_urls: ForkUrls, markers: ResolverMarkers, + workspace_members: BTreeSet, ) -> Self { Self { error, @@ -150,6 +152,7 @@ impl NoSolutionError { incomplete_packages, fork_urls, markers, + workspace_members, } } @@ -211,8 +214,14 @@ impl std::fmt::Display for NoSolutionError { let formatter = PubGrubReportFormatter { available_versions: &self.available_versions, python_requirement: &self.python_requirement, + workspace_members: &self.workspace_members, }; - let report = DefaultStringReporter::report_with_formatter(&self.error, &formatter); + + // Transform the error tree for reporting + let mut tree = self.error.clone(); + collapse_unavailable_workspace_members(&mut tree); + + let report = DefaultStringReporter::report_with_formatter(&tree, &formatter); write!(f, "{report}")?; // Include any additional hints. @@ -232,6 +241,51 @@ impl std::fmt::Display for NoSolutionError { } } +/// Given a [`DerivationTree`], collapse any [`UnavailablePackage::WorkspaceMember`] incompatibilities +/// to avoid saying things like "only ==0.1.0 is available". +fn collapse_unavailable_workspace_members( + tree: &mut DerivationTree, UnavailableReason>, +) { + match tree { + DerivationTree::External(_) => {} + DerivationTree::Derived(derived) => { + match ( + Arc::make_mut(&mut derived.cause1), + Arc::make_mut(&mut derived.cause2), + ) { + // If one node is an unavailable workspace member... + ( + DerivationTree::External(External::Custom( + _, + _, + UnavailableReason::Package(UnavailablePackage::WorkspaceMember), + )), + ref mut other, + ) + | ( + ref mut other, + DerivationTree::External(External::Custom( + _, + _, + UnavailableReason::Package(UnavailablePackage::WorkspaceMember), + )), + ) => { + // First, recursively collapse the other side of the tree + collapse_unavailable_workspace_members(other); + + // Then, replace this node with the other tree + *tree = other.clone(); + } + // If not, just recurse + _ => { + collapse_unavailable_workspace_members(Arc::make_mut(&mut derived.cause1)); + collapse_unavailable_workspace_members(Arc::make_mut(&mut derived.cause2)); + } + } + } + } +} + #[derive(Debug)] pub struct NoSolutionHeader { /// The [`ResolverMarkers`] that caused the failure. diff --git a/crates/uv-resolver/src/manifest.rs b/crates/uv-resolver/src/manifest.rs index 91a2d68e54ec..741ae98d0f13 100644 --- a/crates/uv-resolver/src/manifest.rs +++ b/crates/uv-resolver/src/manifest.rs @@ -1,5 +1,6 @@ use either::Either; use std::borrow::Cow; +use std::collections::BTreeSet; use pep508_rs::MarkerEnvironment; use pypi_types::Requirement; @@ -36,6 +37,9 @@ pub struct Manifest { /// The name of the project. pub(crate) project: Option, + /// Members of the project's workspace. + pub(crate) workspace_members: BTreeSet, + /// The installed packages to exclude from consideration during resolution. /// /// These typically represent packages that are being upgraded or reinstalled @@ -58,6 +62,7 @@ impl Manifest { dev: Vec, preferences: Preferences, project: Option, + workspace_members: Option>, exclusions: Exclusions, lookaheads: Vec, ) -> Self { @@ -68,6 +73,7 @@ impl Manifest { dev, preferences, project, + workspace_members: workspace_members.unwrap_or_default(), exclusions, lookaheads, } @@ -82,6 +88,7 @@ impl Manifest { preferences: Preferences::default(), project: None, exclusions: Exclusions::default(), + workspace_members: BTreeSet::new(), lookaheads: Vec::new(), } } diff --git a/crates/uv-resolver/src/pubgrub/report.rs b/crates/uv-resolver/src/pubgrub/report.rs index 5f8606d2a085..32b235221706 100644 --- a/crates/uv-resolver/src/pubgrub/report.rs +++ b/crates/uv-resolver/src/pubgrub/report.rs @@ -30,6 +30,8 @@ pub(crate) struct PubGrubReportFormatter<'a> { /// The versions that were available for each package pub(crate) python_requirement: &'a PythonRequirement, + + pub(crate) workspace_members: &'a BTreeSet, } impl ReportFormatter, UnavailableReason> @@ -53,12 +55,12 @@ impl ReportFormatter, UnavailableReason> return if let Some(target) = self.python_requirement.target() { format!( "the requested {package} version ({target}) does not satisfy {}", - PackageRange::compatibility(package, set) + self.compatible_range(package, set) ) } else { format!( "the requested {package} version does not satisfy {}", - PackageRange::compatibility(package, set) + self.compatible_range(package, set) ) }; } @@ -69,7 +71,7 @@ impl ReportFormatter, UnavailableReason> return format!( "the current {package} version ({}) does not satisfy {}", self.python_requirement.installed(), - PackageRange::compatibility(package, set) + self.compatible_range(package, set) ); } @@ -86,57 +88,51 @@ impl ReportFormatter, UnavailableReason> if segments == 1 { format!( "only {} is available", - PackageRange::compatibility(package, &complement) + self.compatible_range(package, &complement) ) // Complex case, there are multiple ranges } else { format!( "only the following versions of {} {}", package, - PackageRange::available(package, &complement) + self.availability_range(package, &complement) ) } } } - External::Custom(package, set, reason) => match &**package { - PubGrubPackageInner::Root(Some(name)) => { - format!("{name} cannot be used because {reason}") - } - PubGrubPackageInner::Root(None) => { - format!("your requirements cannot be used because {reason}") - } - _ => match reason { - UnavailableReason::Package(reason) => { - // While there may be a term attached, this error applies to the entire - // package, so we show it for the entire package - format!("{}{reason}", Padded::new("", &package, " ")) - } - UnavailableReason::Version(reason) => { - format!( - "{}{reason}", - Padded::new("", &PackageRange::compatibility(package, set), " ") - ) + External::Custom(package, set, reason) => { + if let Some(root) = self.format_root(package) { + format!("{root} cannot be used because {reason}") + } else { + match reason { + UnavailableReason::Package(reason) => { + // While there may be a term attached, this error applies to the entire + // package, so we show it for the entire package + format!("{}{reason}", Padded::new("", &package, " ")) + } + UnavailableReason::Version(reason) => { + format!( + "{}{reason}", + Padded::new("", &self.compatible_range(package, set), " ") + ) + } } - }, - }, + } + } External::FromDependencyOf(package, package_set, dependency, dependency_set) => { let package_set = self.simplify_set(package_set, package); let dependency_set = self.simplify_set(dependency_set, dependency); - match &**package { - PubGrubPackageInner::Root(Some(name)) => format!( - "{name} depends on {}", - PackageRange::dependency(dependency, &dependency_set) - ), - PubGrubPackageInner::Root(None) => format!( - "you require {}", - PackageRange::dependency(dependency, &dependency_set) - ), - _ => format!( - "{}", - PackageRange::compatibility(package, &package_set) - .depends_on(dependency, &dependency_set), - ), + if let Some(root) = self.format_root_requires(package) { + return format!( + "{root} {}", + self.dependency_range(dependency, &dependency_set) + ); } + format!( + "{}", + self.compatible_range(package, &package_set) + .depends_on(dependency, &dependency_set), + ) } } } @@ -150,25 +146,24 @@ impl ReportFormatter, UnavailableReason> match terms_vec.as_slice() { [] => "the requirements are unsatisfiable".into(), [(root, _)] if matches!(&**(*root), PubGrubPackageInner::Root(_)) => { - "the requirements are unsatisfiable".into() + let root = self.format_root(root).unwrap(); + format!("{root} are unsatisfiable") } [(package, Term::Positive(range))] if matches!(&**(*package), PubGrubPackageInner::Package { .. }) => { let range = self.simplify_set(range, package); - format!( - "{} cannot be used", - PackageRange::compatibility(package, &range) - ) + if let Some(member) = self.format_workspace_member(package) { + format!("{member}'s requirements are unsatisfiable") + } else { + format!("{} cannot be used", self.compatible_range(package, &range)) + } } [(package, Term::Negative(range))] if matches!(&**(*package), PubGrubPackageInner::Package { .. }) => { let range = self.simplify_set(range, package); - format!( - "{} must be used", - PackageRange::compatibility(package, &range) - ) + format!("{} must be used", self.compatible_range(package, &range)) } [(p1, Term::Positive(r1)), (p2, Term::Negative(r2))] => self.format_external( &External::FromDependencyOf((*p1).clone(), r1.clone(), (*p2).clone(), r2.clone()), @@ -180,7 +175,7 @@ impl ReportFormatter, UnavailableReason> let mut result = String::new(); let str_terms: Vec<_> = slice .iter() - .map(|(p, t)| format!("{}", PackageTerm::new(p, t))) + .map(|(p, t)| format!("{}", PackageTerm::new(p, t, self))) .collect(); for (index, term) in str_terms.iter().enumerate() { result.push_str(term); @@ -195,7 +190,7 @@ impl ReportFormatter, UnavailableReason> } } if let [(p, t)] = slice { - if PackageTerm::new(p, t).plural() { + if PackageTerm::new(p, t, self).plural() { result.push_str(" are incompatible"); } else { result.push_str(" is incompatible"); @@ -328,6 +323,88 @@ impl ReportFormatter, UnavailableReason> } impl PubGrubReportFormatter<'_> { + /// Return the formatting for "the root package requires", if the given + /// package is the root package. + /// + /// If not given the root package, returns `None`. + fn format_root_requires(&self, package: &PubGrubPackage) -> Option { + if self.is_workspace() { + if matches!(&**package, PubGrubPackageInner::Root(_)) { + return Some("your workspace requires".to_string()); + } + } + match &**package { + PubGrubPackageInner::Root(Some(name)) => Some(format!("{name} depends on")), + PubGrubPackageInner::Root(None) => Some("you require".to_string()), + _ => None, + } + } + + /// Return the formatting for "the root package", if the given + /// package is the root package. + /// + /// If not given the root package, returns `None`. + fn format_root(&self, package: &PubGrubPackage) -> Option { + if self.is_workspace() { + if matches!(&**package, PubGrubPackageInner::Root(_)) { + return Some("your workspace's requirements".to_string()); + } + } + match &**package { + PubGrubPackageInner::Root(Some(_)) => Some("the requirements".to_string()), + PubGrubPackageInner::Root(None) => Some("the requirements".to_string()), + _ => None, + } + } + + /// Whether the resolution error is for a workspace. + fn is_workspace(&self) -> bool { + !self.workspace_members.is_empty() + } + + /// Return a display name for the package if it is a workspace member. + fn format_workspace_member(&self, package: &PubGrubPackage) -> Option { + match &**package { + PubGrubPackageInner::Package { name, .. } + | PubGrubPackageInner::Extra { name, .. } + | PubGrubPackageInner::Dev { name, .. } => { + if self.workspace_members.contains(name) { + Some(format!("{name}")) + } else { + None + } + } + _ => None, + } + } + + /// Create a [`PackageRange::compatibility`] display with this formatter attached. + fn compatible_range<'a>( + &'a self, + package: &'a PubGrubPackage, + range: &'a Range, + ) -> PackageRange<'a> { + PackageRange::compatibility(package, range, Some(self)) + } + + /// Create a [`PackageRange::dependency`] display with this formatter attached. + fn dependency_range<'a>( + &'a self, + package: &'a PubGrubPackage, + range: &'a Range, + ) -> PackageRange<'a> { + PackageRange::dependency(package, range, Some(self)) + } + + /// Create a [`PackageRange::availability`] display with this formatter attached. + fn availability_range<'a>( + &'a self, + package: &'a PubGrubPackage, + range: &'a Range, + ) -> PackageRange<'a> { + PackageRange::availability(package, range, Some(self)) + } + /// Format two external incompatibilities, combining them if possible. fn format_both_external( &self, @@ -340,33 +417,26 @@ impl PubGrubReportFormatter<'_> { External::FromDependencyOf(package2, _, dependency2, dependency_set2), ) if package1 == package2 => { let dependency_set1 = self.simplify_set(dependency_set1, dependency1); - let dependency1 = PackageRange::dependency(dependency1, &dependency_set1); + let dependency1 = self.dependency_range(dependency1, &dependency_set1); let dependency_set2 = self.simplify_set(dependency_set2, dependency2); - let dependency2 = PackageRange::dependency(dependency2, &dependency_set2); + let dependency2 = self.dependency_range(dependency2, &dependency_set2); - match &**package1 { - PubGrubPackageInner::Root(Some(name)) => format!( - "{name} depends on {}and {}", - Padded::new("", &dependency1, " "), - dependency2, - ), - PubGrubPackageInner::Root(None) => format!( - "you require {}and {}", + if let Some(root) = self.format_root_requires(package1) { + return format!( + "{root} {}and {}", Padded::new("", &dependency1, " "), dependency2, - ), - _ => { - let package_set = self.simplify_set(package_set1, package1); - - format!( - "{}", - PackageRange::compatibility(package1, &package_set) - .depends_on(dependency1.package, &dependency_set1) - .and(dependency2.package, &dependency_set2), - ) - } + ); } + let package_set = self.simplify_set(package_set1, package1); + + format!( + "{}", + self.compatible_range(package1, &package_set) + .depends_on(dependency1.package, &dependency_set1) + .and(dependency2.package, &dependency_set2), + ) } _ => { let external1 = self.format_external(external1); @@ -521,7 +591,7 @@ impl PubGrubReportFormatter<'_> { reason: reason.clone(), }); } - Some(UnavailablePackage::NotFound) => {} + Some(UnavailablePackage::NotFound | UnavailablePackage::WorkspaceMember) => {} None => {} } @@ -716,7 +786,7 @@ impl std::fmt::Display for PubGrubHint { "hint".bold().cyan(), ":".bold(), package.bold(), - PackageRange::compatibility(package, range).bold() + PackageRange::compatibility(package, range, None).bold() ) } Self::NoIndex => { @@ -831,7 +901,7 @@ impl std::fmt::Display for PubGrubHint { "hint".bold().cyan(), ":".bold(), requires_python.bold(), - PackageRange::compatibility(package, package_set).bold(), + PackageRange::compatibility(package, package_set, None).bold(), package_requires_python.bold(), package_requires_python.bold(), ) @@ -844,12 +914,15 @@ impl std::fmt::Display for PubGrubHint { struct PackageTerm<'a> { package: &'a PubGrubPackage, term: &'a Term>, + formatter: &'a PubGrubReportFormatter<'a>, } impl std::fmt::Display for PackageTerm<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match &self.term { - Term::Positive(set) => write!(f, "{}", PackageRange::compatibility(self.package, set)), + Term::Positive(set) => { + write!(f, "{}", self.formatter.compatible_range(self.package, set)) + } Term::Negative(set) => { if let Some(version) = set.as_singleton() { // Note we do not handle the "root" package here but we should never @@ -860,7 +933,8 @@ impl std::fmt::Display for PackageTerm<'_> { write!( f, "{}", - PackageRange::compatibility(self.package, &set.complement()) + self.formatter + .compatible_range(self.package, &set.complement()) ) } } @@ -870,19 +944,29 @@ impl std::fmt::Display for PackageTerm<'_> { impl PackageTerm<'_> { /// Create a new [`PackageTerm`] from a [`PubGrubPackage`] and a [`Term`]. - fn new<'a>(package: &'a PubGrubPackage, term: &'a Term>) -> PackageTerm<'a> { - PackageTerm { package, term } + fn new<'a>( + package: &'a PubGrubPackage, + term: &'a Term>, + formatter: &'a PubGrubReportFormatter<'a>, + ) -> PackageTerm<'a> { + PackageTerm { + package, + term, + formatter, + } } /// Returns `true` if the predicate following this package term should be singular or plural. fn plural(&self) -> bool { match self.term { - Term::Positive(set) => PackageRange::compatibility(self.package, set).plural(), + Term::Positive(set) => self.formatter.compatible_range(self.package, set).plural(), Term::Negative(set) => { if set.as_singleton().is_some() { false } else { - PackageRange::compatibility(self.package, &set.complement()).plural() + self.formatter + .compatible_range(self.package, &set.complement()) + .plural() } } } @@ -903,9 +987,49 @@ struct PackageRange<'a> { package: &'a PubGrubPackage, range: &'a Range, kind: PackageRangeKind, + formatter: Option<&'a PubGrubReportFormatter<'a>>, } impl PackageRange<'_> { + fn compatibility<'a>( + package: &'a PubGrubPackage, + range: &'a Range, + formatter: Option<&'a PubGrubReportFormatter<'a>>, + ) -> PackageRange<'a> { + PackageRange { + package, + range, + kind: PackageRangeKind::Compatibility, + formatter, + } + } + + fn dependency<'a>( + package: &'a PubGrubPackage, + range: &'a Range, + formatter: Option<&'a PubGrubReportFormatter<'a>>, + ) -> PackageRange<'a> { + PackageRange { + package, + range, + kind: PackageRangeKind::Dependency, + formatter, + } + } + + fn availability<'a>( + package: &'a PubGrubPackage, + range: &'a Range, + formatter: Option<&'a PubGrubReportFormatter<'a>>, + ) -> PackageRange<'a> { + PackageRange { + package, + range, + kind: PackageRangeKind::Available, + formatter, + } + } + /// Returns a boolean indicating if the predicate following this package range should /// be singular or plural e.g. if false use " depends on <...>" and /// if true use " depend on <...>" @@ -930,11 +1054,20 @@ impl PackageRange<'_> { impl std::fmt::Display for PackageRange<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { // Exit early for the root package — the range is not meaningful - let package = match &**self.package { - PubGrubPackageInner::Root(Some(name)) => return write!(f, "{name}"), - PubGrubPackageInner::Root(None) => return write!(f, "your requirements"), - _ => self.package, - }; + if let Some(root) = self + .formatter + .and_then(|formatter| formatter.format_root(self.package)) + { + return write!(f, "{root}"); + } + // Exit early for workspace members, only a single version is available + if let Some(member) = self + .formatter + .and_then(|formatter| formatter.format_workspace_member(self.package)) + { + return write!(f, "{member}"); + } + let package = self.package; if self.range.is_empty() { return write!(f, "{package} ∅"); @@ -982,33 +1115,6 @@ impl std::fmt::Display for PackageRange<'_> { } impl PackageRange<'_> { - fn compatibility<'a>( - package: &'a PubGrubPackage, - range: &'a Range, - ) -> PackageRange<'a> { - PackageRange { - package, - range, - kind: PackageRangeKind::Compatibility, - } - } - - fn dependency<'a>(package: &'a PubGrubPackage, range: &'a Range) -> PackageRange<'a> { - PackageRange { - package, - range, - kind: PackageRangeKind::Dependency, - } - } - - fn available<'a>(package: &'a PubGrubPackage, range: &'a Range) -> PackageRange<'a> { - PackageRange { - package, - range, - kind: PackageRangeKind::Available, - } - } - fn depends_on<'a>( &'a self, package: &'a PubGrubPackage, @@ -1016,7 +1122,12 @@ impl PackageRange<'_> { ) -> DependsOn<'a> { DependsOn { package: self, - dependency1: PackageRange::dependency(package, range), + dependency1: PackageRange { + package, + range, + kind: PackageRangeKind::Dependency, + formatter: self.formatter, + }, dependency2: None, } } @@ -1035,7 +1146,12 @@ impl<'a> DependsOn<'a> { /// /// Note this overwrites previous calls to `DependsOn::and`. fn and(mut self, package: &'a PubGrubPackage, range: &'a Range) -> DependsOn<'a> { - self.dependency2 = Some(PackageRange::dependency(package, range)); + self.dependency2 = Some(PackageRange { + package, + range, + kind: PackageRangeKind::Dependency, + formatter: self.package.formatter, + }); self } } diff --git a/crates/uv-resolver/src/resolver/availability.rs b/crates/uv-resolver/src/resolver/availability.rs index 1b01cb25f5da..f40dfe499190 100644 --- a/crates/uv-resolver/src/resolver/availability.rs +++ b/crates/uv-resolver/src/resolver/availability.rs @@ -74,6 +74,8 @@ pub(crate) enum UnavailablePackage { InvalidMetadata(String), /// The package has an invalid structure. InvalidStructure(String), + /// No other versions of the package can be used because it is a workspace member + WorkspaceMember, } impl UnavailablePackage { @@ -85,6 +87,7 @@ impl UnavailablePackage { UnavailablePackage::MissingMetadata => "does not include a `METADATA` file", UnavailablePackage::InvalidMetadata(_) => "has invalid metadata", UnavailablePackage::InvalidStructure(_) => "has an invalid package format", + UnavailablePackage::WorkspaceMember => "is a workspace member", } } } diff --git a/crates/uv-resolver/src/resolver/mod.rs b/crates/uv-resolver/src/resolver/mod.rs index 1963dae41048..52bdd861e404 100644 --- a/crates/uv-resolver/src/resolver/mod.rs +++ b/crates/uv-resolver/src/resolver/mod.rs @@ -102,6 +102,7 @@ struct ResolverState { hasher: HashStrategy, markers: ResolverMarkers, python_requirement: PythonRequirement, + workspace_members: BTreeSet, /// This is derived from `PythonRequirement` once at initialization /// time. It's used in universal mode to filter our dependencies with /// a `python_version` marker expression that has no overlap with the @@ -225,6 +226,7 @@ impl ), groups: Groups::from_manifest(&manifest, markers.marker_environment()), project: manifest.project, + workspace_members: manifest.workspace_members, requirements: manifest.requirements, constraints: manifest.constraints, overrides: manifest.overrides, @@ -449,8 +451,23 @@ impl ResolverState ResolverState( dev: Vec, source_trees: Vec, mut project: Option, + workspace_members: Option>, extras: &ExtrasSpecification, preferences: Vec, installed_packages: InstalledPackages, @@ -230,6 +232,7 @@ pub(crate) async fn resolve( dev, preferences, project, + workspace_members, exclusions, lookaheads, ); diff --git a/crates/uv/src/commands/pip/sync.rs b/crates/uv/src/commands/pip/sync.rs index 32d58db418dc..89466fdaf4ec 100644 --- a/crates/uv/src/commands/pip/sync.rs +++ b/crates/uv/src/commands/pip/sync.rs @@ -288,6 +288,7 @@ pub(crate) async fn pip_sync( dev, source_trees, project, + None, &extras, preferences, site_packages.clone(), diff --git a/crates/uv/src/commands/project/lock.rs b/crates/uv/src/commands/project/lock.rs index b928cbad6e8b..0c64a2f6cc6f 100644 --- a/crates/uv/src/commands/project/lock.rs +++ b/crates/uv/src/commands/project/lock.rs @@ -596,6 +596,7 @@ async fn do_lock( dev, source_trees, None, + Some(workspace.packages().keys().cloned().collect()), &extras, preferences, EmptyInstalledPackages, diff --git a/crates/uv/src/commands/project/mod.rs b/crates/uv/src/commands/project/mod.rs index 52a434ef4489..4cf74c6b41cd 100644 --- a/crates/uv/src/commands/project/mod.rs +++ b/crates/uv/src/commands/project/mod.rs @@ -630,6 +630,7 @@ pub(crate) async fn resolve_environment<'a>( dev, source_trees, project, + None, &extras, preferences, EmptyInstalledPackages, @@ -952,6 +953,7 @@ pub(crate) async fn update_environment( dev, source_trees, project, + None, &extras, preferences, site_packages.clone(), diff --git a/crates/uv/tests/edit.rs b/crates/uv/tests/edit.rs index 23ac75268a50..2cca891ce407 100644 --- a/crates/uv/tests/edit.rs +++ b/crates/uv/tests/edit.rs @@ -2578,8 +2578,8 @@ fn add_error() -> Result<()> { ----- stderr ----- warning: `uv add` is experimental and may change without warning × No solution found when resolving dependencies: - ╰─▶ Because there are no versions of xyz and project==0.1.0 depends on xyz, we can conclude that project==0.1.0 cannot be used. - And because only project==0.1.0 is available and you require project, we can conclude that the requirements are unsatisfiable. + ╰─▶ Because there are no versions of xyz and project depends on xyz, we can conclude that project's requirements are unsatisfiable. + And because your workspace requires project, we can conclude that your workspace's requirements are unsatisfiable. help: If this is intentional, run `uv add --frozen` to skip the lock and sync steps. "###); diff --git a/crates/uv/tests/lock.rs b/crates/uv/tests/lock.rs index 38cb17ea0a91..33170b3f6461 100644 --- a/crates/uv/tests/lock.rs +++ b/crates/uv/tests/lock.rs @@ -2748,8 +2748,7 @@ fn lock_requires_python() -> Result<()> { pygls>=1.1.0,<1.3.0 pygls>1.3.0 cannot be used, we can conclude that pygls>=1.1.0 cannot be used. - And because project==0.1.0 depends on pygls>=1.1.0, we can conclude that project==0.1.0 cannot be used. - And because only project==0.1.0 is available and you require project, we can conclude that the requirements are unsatisfiable. + And because project depends on pygls>=1.1.0 and your workspace requires project, we can conclude that your workspace's requirements are unsatisfiable. hint: The `requires-python` value (>=3.7) includes Python versions that are not supported by your dependencies (e.g., pygls>=1.1.0,<=1.2.1 only supports >=3.7.9, <4). Consider using a more restrictive `requires-python` value (like >=3.7.9, <4). "###); @@ -5016,8 +5015,8 @@ fn lock_requires_python_no_wheels() -> Result<()> { ----- stderr ----- warning: `uv lock` is experimental and may change without warning × No solution found when resolving dependencies: - ╰─▶ Because dearpygui==1.9.1 has no wheels with a matching Python ABI tag and project==0.1.0 depends on dearpygui==1.9.1, we can conclude that project==0.1.0 cannot be used. - And because only project==0.1.0 is available and you require project, we can conclude that the requirements are unsatisfiable. + ╰─▶ Because dearpygui==1.9.1 has no wheels with a matching Python ABI tag and project depends on dearpygui==1.9.1, we can conclude that project's requirements are unsatisfiable. + And because your workspace requires project, we can conclude that your workspace's requirements are unsatisfiable. "###); Ok(()) @@ -8211,8 +8210,8 @@ fn unconditional_overlapping_marker_disjoint_version_constraints() -> Result<()> ----- stderr ----- warning: `uv lock` is experimental and may change without warning × No solution found when resolving dependencies for split (python_version > '3.10'): - ╰─▶ Because only datasets{python_version > '3.10'}<2.19 is available and project==0.1.0 depends on datasets{python_version > '3.10'}>=2.19, we can conclude that project==0.1.0 cannot be used. - And because only project==0.1.0 is available and you require project, we can conclude that the requirements are unsatisfiable. + ╰─▶ Because only datasets{python_version > '3.10'}<2.19 is available and project depends on datasets{python_version > '3.10'}>=2.19, we can conclude that project's requirements are unsatisfiable. + And because your workspace requires project, we can conclude that your workspace's requirements are unsatisfiable. "###); Ok(()) diff --git a/crates/uv/tests/lock_scenarios.rs b/crates/uv/tests/lock_scenarios.rs index 558cefe16d7d..4e76f13aac7e 100644 --- a/crates/uv/tests/lock_scenarios.rs +++ b/crates/uv/tests/lock_scenarios.rs @@ -468,13 +468,13 @@ fn conflict_in_fork() -> Result<()> { warning: `uv lock` is experimental and may change without warning × No solution found when resolving dependencies for split (sys_platform == 'darwin'): ╰─▶ Because only package-b==1.0.0 is available and package-b==1.0.0 depends on package-d==1, we can conclude that all versions of package-b depend on package-d==1. - And because package-c==1.0.0 depends on package-d==2 and only package-c==1.0.0 is available, we can conclude that all versions of package-b and all versions of package-c are incompatible. - And because package-a{sys_platform == 'darwin'}==1.0.0 depends on package-b and package-c, we can conclude that package-a{sys_platform == 'darwin'}==1.0.0 cannot be used. - And because only the following versions of package-a{sys_platform == 'darwin'} are available: + And because package-c==1.0.0 depends on package-d==2, we can conclude that all versions of package-b and package-c==1.0.0 are incompatible. + And because only package-c==1.0.0 is available and package-a{sys_platform == 'darwin'}==1.0.0 depends on package-b, we can conclude that package-a{sys_platform == 'darwin'}==1.0.0 and all versions of package-c are incompatible. + And because package-a{sys_platform == 'darwin'}==1.0.0 depends on package-c and only the following versions of package-a{sys_platform == 'darwin'} are available: package-a{sys_platform == 'darwin'}==1.0.0 package-a{sys_platform == 'darwin'}>=2 - and project==0.1.0 depends on package-a{sys_platform == 'darwin'}<2, we can conclude that project==0.1.0 cannot be used. - And because only project==0.1.0 is available and you require project, we can conclude that the requirements are unsatisfiable. + we can conclude that package-a{sys_platform == 'darwin'}<2 is incompatible. + And because project depends on package-a{sys_platform == 'darwin'}<2 and your workspace requires project, we can conclude that your workspace's requirements are unsatisfiable. "### ); @@ -537,8 +537,8 @@ fn fork_conflict_unsatisfiable() -> Result<()> { ----- stderr ----- warning: `uv lock` is experimental and may change without warning × No solution found when resolving dependencies: - ╰─▶ Because project==0.1.0 depends on package-a>=2 and package-a<2, we can conclude that project==0.1.0 cannot be used. - And because only project==0.1.0 is available and you require project, we can conclude that the requirements are unsatisfiable. + ╰─▶ Because project depends on package-a>=2 and package-a<2, we can conclude that project's requirements are unsatisfiable. + And because your workspace requires project, we can conclude that your workspace's requirements are unsatisfiable. "### ); @@ -1262,8 +1262,8 @@ fn fork_marker_disjoint() -> Result<()> { ----- stderr ----- warning: `uv lock` is experimental and may change without warning × No solution found when resolving dependencies for split (sys_platform == 'linux'): - ╰─▶ Because project==0.1.0 depends on package-a{sys_platform == 'linux'}>=2 and package-a{sys_platform == 'linux'}<2, we can conclude that project==0.1.0 cannot be used. - And because only project==0.1.0 is available and you require project, we can conclude that the requirements are unsatisfiable. + ╰─▶ Because project depends on package-a{sys_platform == 'linux'}>=2 and package-a{sys_platform == 'linux'}<2, we can conclude that project's requirements are unsatisfiable. + And because your workspace requires project, we can conclude that your workspace's requirements are unsatisfiable. "### ); @@ -3024,8 +3024,8 @@ fn fork_non_local_fork_marker_direct() -> Result<()> { warning: `uv lock` is experimental and may change without warning × No solution found when resolving dependencies: ╰─▶ Because package-b{sys_platform == 'darwin'}==1.0.0 depends on package-c>=2.0.0 and package-a{sys_platform == 'linux'}==1.0.0 depends on package-c<2.0.0, we can conclude that package-a{sys_platform == 'linux'}==1.0.0 and package-b{sys_platform == 'darwin'}==1.0.0 are incompatible. - And because project==0.1.0 depends on package-a{sys_platform == 'linux'}==1.0.0 and package-b{sys_platform == 'darwin'}==1.0.0, we can conclude that project==0.1.0 cannot be used. - And because only project==0.1.0 is available and you require project, we can conclude that the requirements are unsatisfiable. + And because project depends on package-a{sys_platform == 'linux'}==1.0.0, we can conclude that project and package-b{sys_platform == 'darwin'}==1.0.0 are incompatible. + And because project depends on package-b{sys_platform == 'darwin'}==1.0.0 and your workspace requires project, we can conclude that your workspace's requirements are unsatisfiable. "### ); @@ -3101,9 +3101,9 @@ fn fork_non_local_fork_marker_transitive() -> Result<()> { And because only the following versions of package-c{sys_platform == 'linux'} are available: package-c{sys_platform == 'linux'}==1.0.0 package-c{sys_platform == 'linux'}>=2.0.0 - and package-a==1.0.0 depends on package-c{sys_platform == 'linux'}<2.0.0, we can conclude that package-a==1.0.0 and package-b==1.0.0 are incompatible. - And because project==0.1.0 depends on package-a==1.0.0 and package-b==1.0.0, we can conclude that project==0.1.0 cannot be used. - And because only project==0.1.0 is available and you require project, we can conclude that the requirements are unsatisfiable. + we can conclude that package-b==1.0.0 and package-c{sys_platform == 'linux'}<2.0.0 are incompatible. + And because package-a==1.0.0 depends on package-c{sys_platform == 'linux'}<2.0.0 and project depends on package-a==1.0.0, we can conclude that package-b==1.0.0 and project are incompatible. + And because project depends on package-b==1.0.0 and your workspace requires project, we can conclude that your workspace's requirements are unsatisfiable. "### ); diff --git a/crates/uv/tests/pip_install.rs b/crates/uv/tests/pip_install.rs index 78351b7c571b..23c1fe60b000 100644 --- a/crates/uv/tests/pip_install.rs +++ b/crates/uv/tests/pip_install.rs @@ -387,7 +387,7 @@ werkzeug==3.0.1 ----- stderr ----- × No solution found when resolving dependencies: - ╰─▶ Because flask==3.0.2 depends on click>=8.1.3 and you require click==7.0.0, we can conclude that your requirements and flask==3.0.2 are incompatible. + ╰─▶ Because flask==3.0.2 depends on click>=8.1.3 and you require click==7.0.0, we can conclude that the requirements and flask==3.0.2 are incompatible. And because you require flask==3.0.2, we can conclude that the requirements are unsatisfiable. "### ); diff --git a/crates/uv/tests/workspace.rs b/crates/uv/tests/workspace.rs index f4ebfc3d387c..6c8a32fe0117 100644 --- a/crates/uv/tests/workspace.rs +++ b/crates/uv/tests/workspace.rs @@ -1008,8 +1008,8 @@ fn workspace_inherit_sources() -> Result<()> { ----- stderr ----- Using Python 3.12.[X] interpreter at: [PYTHON-3.12] × No solution found when resolving dependencies: - ╰─▶ Because library was not found in the cache and leaf==0.1.0 depends on library, we can conclude that leaf==0.1.0 cannot be used. - And because only leaf==0.1.0 is available and you require leaf, we can conclude that the requirements are unsatisfiable. + ╰─▶ Because library was not found in the cache and leaf depends on library, we can conclude that leaf's requirements are unsatisfiable. + And because your workspace requires leaf, we can conclude that your workspace's requirements are unsatisfiable. hint: Packages were unavailable because the network was disabled "### @@ -1200,8 +1200,8 @@ fn workspace_unsatisfiable_member_dependencies() -> Result<()> { ----- stderr ----- Using Python 3.12.[X] interpreter at: [PYTHON-3.12] × No solution found when resolving dependencies: - ╰─▶ Because only httpx<=9999 is available and leaf==0.1.0 depends on httpx>9999, we can conclude that leaf==0.1.0 cannot be used. - And because only leaf==0.1.0 is available and you require leaf, we can conclude that the requirements are unsatisfiable. + ╰─▶ Because only httpx<=9999 is available and leaf depends on httpx>9999, we can conclude that leaf's requirements are unsatisfiable. + And because your workspace requires leaf, we can conclude that your workspace's requirements are unsatisfiable. "### ); @@ -1256,9 +1256,8 @@ fn workspace_unsatisfiable_member_dependencies_conflicting() -> Result<()> { ----- stderr ----- Using Python 3.12.[X] interpreter at: [PYTHON-3.12] × No solution found when resolving dependencies: - ╰─▶ Because only bar==0.1.0 is available and bar==0.1.0 depends on anyio==4.2.0, we can conclude that all versions of bar depend on anyio==4.2.0. - And because foo==0.1.0 depends on anyio==4.1.0 and only foo==0.1.0 is available, we can conclude that all versions of bar and all versions of foo are incompatible. - And because you require bar and foo, we can conclude that the requirements are unsatisfiable. + ╰─▶ Because bar depends on anyio==4.2.0 and foo depends on anyio==4.1.0, we can conclude that bar and foo are incompatible. + And because your workspace requires bar and foo, we can conclude that your workspace's requirements are unsatisfiable. "### ); @@ -1324,9 +1323,8 @@ fn workspace_unsatisfiable_member_dependencies_conflicting_threeway() -> Result< ----- stderr ----- Using Python 3.12.[X] interpreter at: [PYTHON-3.12] × No solution found when resolving dependencies: - ╰─▶ Because only bird==0.1.0 is available and bird==0.1.0 depends on anyio==4.3.0, we can conclude that all versions of bird depend on anyio==4.3.0. - And because knot==0.1.0 depends on anyio==4.2.0 and only knot==0.1.0 is available, we can conclude that all versions of bird and all versions of knot are incompatible. - And because you require bird and knot, we can conclude that the requirements are unsatisfiable. + ╰─▶ Because bird depends on anyio==4.3.0 and knot depends on anyio==4.2.0, we can conclude that bird and knot are incompatible. + And because your workspace requires bird and knot, we can conclude that your workspace's requirements are unsatisfiable. "### );