diff --git a/crates/uv-distribution-types/src/prioritized_distribution.rs b/crates/uv-distribution-types/src/prioritized_distribution.rs index 54503a3317280..daa1f38b1fbdf 100644 --- a/crates/uv-distribution-types/src/prioritized_distribution.rs +++ b/crates/uv-distribution-types/src/prioritized_distribution.rs @@ -142,9 +142,7 @@ impl Display for IncompatibleDist { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { Self::Wheel(incompatibility) => match incompatibility { - IncompatibleWheel::NoBinary => { - f.write_str("no source distribution and using wheels is disabled") - } + IncompatibleWheel::NoBinary => f.write_str("no source distribution"), IncompatibleWheel::Tag(tag) => match tag { IncompatibleTag::Invalid => f.write_str("no wheels with valid tags"), IncompatibleTag::Python => { @@ -175,9 +173,7 @@ impl Display for IncompatibleDist { } }, Self::Source(incompatibility) => match incompatibility { - IncompatibleSource::NoBuild => { - f.write_str("no usable wheels and building from source is disabled") - } + IncompatibleSource::NoBuild => f.write_str("no usable wheels"), IncompatibleSource::Yanked(yanked) => match yanked { Yanked::Bool(_) => f.write_str("yanked"), Yanked::Reason(reason) => write!( diff --git a/crates/uv-resolver/src/pubgrub/report.rs b/crates/uv-resolver/src/pubgrub/report.rs index 3e90c6ae85a84..13cf7bb7b69c2 100644 --- a/crates/uv-resolver/src/pubgrub/report.rs +++ b/crates/uv-resolver/src/pubgrub/report.rs @@ -9,7 +9,10 @@ use pubgrub::{DerivationTree, Derived, External, Map, Range, ReportFormatter, Te use rustc_hash::FxHashMap; use uv_configuration::IndexStrategy; -use uv_distribution_types::{Index, IndexCapabilities, IndexLocations, IndexUrl}; +use uv_distribution_types::{ + IncompatibleDist, IncompatibleSource, IncompatibleWheel, Index, IndexCapabilities, + IndexLocations, IndexUrl, +}; use uv_normalize::PackageName; use uv_pep440::{Version, VersionSpecifiers}; @@ -18,7 +21,9 @@ use crate::error::ErrorTree; use crate::fork_urls::ForkUrls; use crate::prerelease::AllowPrerelease; use crate::python_requirement::{PythonRequirement, PythonRequirementSource}; -use crate::resolver::{MetadataUnavailable, UnavailablePackage, UnavailableReason}; +use crate::resolver::{ + MetadataUnavailable, UnavailablePackage, UnavailableReason, UnavailableVersion, +}; use crate::{Flexibility, Options, RequiresPython, ResolverEnvironment}; use super::{PubGrubPackage, PubGrubPackageInner, PubGrubPython}; @@ -556,9 +561,58 @@ impl PubGrubReportFormatter<'_> { output_hints: &mut IndexSet, ) { match derivation_tree { - DerivationTree::External( - External::Custom(package, set, _) | External::NoVersions(package, set), - ) => { + DerivationTree::External(External::Custom(package, set, reason)) => { + if let PubGrubPackageInner::Package { name, .. } = &**package { + // Check for no versions due to pre-release options. + if options.flexibility == Flexibility::Configurable { + if !fork_urls.contains_key(name) { + self.prerelease_available_hint( + package, + name, + set, + selector, + env, + output_hints, + ); + } + } + + // Check for no versions due to no `--find-links` flat index. + Self::index_hints( + package, + name, + set, + selector, + index_locations, + index_capabilities, + available_indexes, + unavailable_packages, + incomplete_packages, + output_hints, + ); + } + + // Check for unavailable versions due to `--no-build` or `--no-binary`. + if let UnavailableReason::Version(UnavailableVersion::IncompatibleDist( + incompatibility, + )) = reason + { + match incompatibility { + IncompatibleDist::Wheel(IncompatibleWheel::NoBinary) => { + output_hints.insert(PubGrubHint::NoBinary { + package: package.clone(), + }); + } + IncompatibleDist::Source(IncompatibleSource::NoBuild) => { + output_hints.insert(PubGrubHint::NoBuild { + package: package.clone(), + }); + } + _ => {} + } + } + } + DerivationTree::External(External::NoVersions(package, set)) => { if let PubGrubPackageInner::Package { name, .. } = &**package { // Check for no versions due to pre-release options. if options.flexibility == Flexibility::Configurable { @@ -954,6 +1008,10 @@ pub(crate) enum PubGrubHint { // excluded from `PartialEq` and `Hash` next_index: IndexUrl, }, + /// No wheels are available for a package, and using source distributions was disabled. + NoBuild { package: PubGrubPackage }, + /// No source distributions are available for a package, and using pre-built wheels was disabled. + NoBinary { package: PubGrubPackage }, /// An index returned an Unauthorized (401) response. UnauthorizedIndex { index: IndexUrl }, /// An index returned a Forbidden (403) response. @@ -1013,6 +1071,12 @@ enum PubGrubHintCore { ForbiddenIndex { index: IndexUrl, }, + NoBuild { + package: PubGrubPackage, + }, + NoBinary { + package: PubGrubPackage, + }, } impl From for PubGrubHintCore { @@ -1068,6 +1132,8 @@ impl From for PubGrubHintCore { PubGrubHint::UncheckedIndex { package, .. } => Self::UncheckedIndex { package }, PubGrubHint::UnauthorizedIndex { index } => Self::UnauthorizedIndex { index }, PubGrubHint::ForbiddenIndex { index } => Self::ForbiddenIndex { index }, + PubGrubHint::NoBuild { package } => Self::NoBuild { package }, + PubGrubHint::NoBinary { package } => Self::NoBinary { package }, } } } @@ -1342,6 +1408,24 @@ impl std::fmt::Display for PubGrubHint { "403 Forbidden".red(), ) } + Self::NoBuild { package } => { + write!( + f, + "{}{} Wheels are required for `{}` because building from source is disabled", + "hint".bold().cyan(), + ":".bold(), + package.cyan(), + ) + } + Self::NoBinary { package } => { + write!( + f, + "{}{} A source distributions is required for `{}` because using pre-built wheels is disabled", + "hint".bold().cyan(), + ":".bold(), + package.cyan(), + ) + } } } } diff --git a/crates/uv/tests/it/pip_compile.rs b/crates/uv/tests/it/pip_compile.rs index 7df2cc33e90f2..a5bb819b2043f 100644 --- a/crates/uv/tests/it/pip_compile.rs +++ b/crates/uv/tests/it/pip_compile.rs @@ -12807,8 +12807,10 @@ fn no_binary_only_binary() -> Result<()> { ----- stderr ----- × No solution found when resolving dependencies: - ╰─▶ Because only source-distribution>=0.0.1 is available and source-distribution==0.0.1 has no usable wheels and building from source is disabled, we can conclude that source-distribution<=0.0.1 cannot be used. + ╰─▶ Because only source-distribution>=0.0.1 is available and source-distribution==0.0.1 has no usable wheels, we can conclude that source-distribution<=0.0.1 cannot be used. And because you require source-distribution<=0.0.1, we can conclude that your requirements are unsatisfiable. + + hint: Wheels are required for `source-distribution` because building from source is disabled "### ); diff --git a/crates/uv/tests/it/pip_install.rs b/crates/uv/tests/it/pip_install.rs index 1692f57b57ebd..5469e401355f7 100644 --- a/crates/uv/tests/it/pip_install.rs +++ b/crates/uv/tests/it/pip_install.rs @@ -2181,7 +2181,7 @@ fn install_only_binary_all_and_no_binary_all() { anyio>=3.0.0,<=3.6.2 anyio>=3.7.0,<=3.7.1 anyio>=4.0.0 - have no usable wheels and building from source is disabled, we can conclude that all of: + have no usable wheels, we can conclude that all of: anyio>=1.0.0,<=1.4.0 anyio>=2.0.0,<=2.2.0 anyio>=3.0.0,<=3.6.2 @@ -2191,6 +2191,8 @@ fn install_only_binary_all_and_no_binary_all() { And because you require anyio, we can conclude that your requirements are unsatisfiable. hint: Pre-releases are available for `anyio` in the requested range (e.g., 4.0.0rc1), but pre-releases weren't enabled (try: `--prerelease=allow`) + + hint: Wheels are required for `anyio` because building from source is disabled "### ); @@ -2281,7 +2283,9 @@ fn only_binary_requirements_txt() { ----- stderr ----- × No solution found when resolving dependencies: - ╰─▶ Because django-allauth==0.51.0 has no usable wheels and building from source is disabled and you require django-allauth==0.51.0, we can conclude that your requirements are unsatisfiable. + ╰─▶ Because django-allauth==0.51.0 has no usable wheels and you require django-allauth==0.51.0, we can conclude that your requirements are unsatisfiable. + + hint: Wheels are required for `django-allauth` because building from source is disabled "### ); } diff --git a/crates/uv/tests/it/pip_install_scenarios.rs b/crates/uv/tests/it/pip_install_scenarios.rs index 996d48f7c1162..206d4618f45df 100644 --- a/crates/uv/tests/it/pip_install_scenarios.rs +++ b/crates/uv/tests/it/pip_install_scenarios.rs @@ -4202,8 +4202,10 @@ fn no_wheels_no_build() { ----- stderr ----- × No solution found when resolving dependencies: - ╰─▶ Because only package-a==1.0.0 is available and package-a==1.0.0 has no usable wheels and building from source is disabled, we can conclude that all versions of package-a cannot be used. + ╰─▶ Because only package-a==1.0.0 is available and package-a==1.0.0 has no usable wheels, we can conclude that all versions of package-a cannot be used. And because you require package-a, we can conclude that your requirements are unsatisfiable. + + hint: Wheels are required for `package-a` because building from source is disabled "###); assert_not_installed(&context.venv, "no_wheels_no_build_a", &context.temp_dir); @@ -4310,8 +4312,10 @@ fn only_wheels_no_binary() { ----- stderr ----- × No solution found when resolving dependencies: - ╰─▶ Because only package-a==1.0.0 is available and package-a==1.0.0 has no source distribution and using wheels is disabled, we can conclude that all versions of package-a cannot be used. + ╰─▶ Because only package-a==1.0.0 is available and package-a==1.0.0 has no source distribution, we can conclude that all versions of package-a cannot be used. And because you require package-a, we can conclude that your requirements are unsatisfiable. + + hint: A source distributions is required for `package-a` because using pre-built wheels is disabled "###); assert_not_installed(&context.venv, "only_wheels_no_binary_a", &context.temp_dir);