Skip to content

Commit

Permalink
Add failure hint
Browse files Browse the repository at this point in the history
  • Loading branch information
charliermarsh committed Nov 3, 2024
1 parent 7a9a9f5 commit d24c42c
Show file tree
Hide file tree
Showing 8 changed files with 214 additions and 135 deletions.
3 changes: 1 addition & 2 deletions crates/uv-distribution/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,7 @@ pub enum Error {
NotFound(Url),
#[error("Attempted to re-extract the source distribution for `{0}`, but the hashes didn't match. Run `{}` to clear the cache.", "uv cache clean".green())]
CacheHeal(String),

#[error("The source distribution requires Python {0}, but the current Python version is {1}")]
#[error("The source distribution requires Python {0}, but {1} is installed")]
RequiresPython(VersionSpecifiers, Version),

/// A generic request middleware error happened while making a request.
Expand Down
2 changes: 1 addition & 1 deletion crates/uv-distribution/src/source/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ use uv_distribution_types::{
use uv_extract::hash::Hasher;
use uv_fs::{rename_with_retry, write_atomic, LockedFile};
use uv_metadata::read_archive_metadata;
use uv_pep440::{release_specifiers_to_ranges, Version};
use uv_pep440::release_specifiers_to_ranges;
use uv_platform_tags::Tags;
use uv_pypi_types::{HashDigest, Metadata12, RequiresTxt, ResolutionMetadata};
use uv_types::{BuildContext, SourceBuildTrait};
Expand Down
45 changes: 43 additions & 2 deletions crates/uv-resolver/src/pubgrub/report.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use rustc_hash::FxHashMap;
use uv_configuration::IndexStrategy;
use uv_distribution_types::{Index, IndexCapabilities, IndexLocations, IndexUrl};
use uv_normalize::PackageName;
use uv_pep440::Version;
use uv_pep440::{Version, VersionSpecifiers};

use crate::candidate_selector::CandidateSelector;
use crate::error::ErrorTree;
Expand Down Expand Up @@ -699,7 +699,14 @@ impl PubGrubReportFormatter<'_> {
reason: reason.clone(),
});
}
IncompletePackage::RequiresPython(_) => {}
IncompletePackage::RequiresPython(requires_python, python_version) => {
hints.insert(PubGrubHint::IncompatibleBuildRequirement {
package: package.clone(),
version: version.clone(),
requires_python: requires_python.clone(),
python_version: python_version.clone(),
});
}
}
break;
}
Expand Down Expand Up @@ -863,6 +870,17 @@ pub(crate) enum PubGrubHint {
// excluded from `PartialEq` and `Hash`
reason: String,
},
/// The source distribution has a `requires-python` requirement that is not met by the installed
/// Python version (and static metadata is not available).
IncompatibleBuildRequirement {
package: PubGrubPackage,
// excluded from `PartialEq` and `Hash`
version: Version,
// excluded from `PartialEq` and `Hash`
requires_python: VersionSpecifiers,
// excluded from `PartialEq` and `Hash`
python_version: Version,
},
/// The `Requires-Python` requirement was not satisfied.
RequiresPython {
source: PythonRequirementSource,
Expand Down Expand Up @@ -933,6 +951,9 @@ enum PubGrubHintCore {
InvalidVersionStructure {
package: PubGrubPackage,
},
IncompatibleBuildRequirement {
package: PubGrubPackage,
},
RequiresPython {
source: PythonRequirementSource,
requires_python: RequiresPython,
Expand Down Expand Up @@ -986,6 +1007,9 @@ impl From<PubGrubHint> for PubGrubHintCore {
PubGrubHint::InvalidVersionStructure { package, .. } => {
Self::InvalidVersionStructure { package }
}
PubGrubHint::IncompatibleBuildRequirement { package, .. } => {
Self::IncompatibleBuildRequirement { package }
}
PubGrubHint::RequiresPython {
source,
requires_python,
Expand Down Expand Up @@ -1188,6 +1212,23 @@ impl std::fmt::Display for PubGrubHint {
package_requires_python.bold(),
)
}
Self::IncompatibleBuildRequirement {
package,
version,
requires_python,
python_version,
} => {
write!(
f,
"{}{} The source distribution for {}=={} does not include static metadata. Generating metadata for this package requires Python {}, but Python {} is installed.",
"hint".bold().cyan(),
":".bold(),
package.bold(),
version.bold(),
requires_python.bold(),
python_version.bold(),
)
}
Self::RequiresPython {
source: PythonRequirementSource::Interpreter,
requires_python: _,
Expand Down
14 changes: 8 additions & 6 deletions crates/uv-resolver/src/resolver/availability.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::fmt::{Display, Formatter};

use uv_distribution_types::IncompatibleDist;
use uv_pep440::Version;
use uv_pep440::{Version, VersionSpecifiers};

/// The reason why a package or a version cannot be used.
#[derive(Debug, Clone, Eq, PartialEq)]
Expand Down Expand Up @@ -42,7 +42,7 @@ pub(crate) enum UnavailableVersion {
Offline,
/// The source distribution has a `requires-python` requirement that is not met by the installed
/// Python version (and static metadata is not available).
RequiresPython,
RequiresPython(VersionSpecifiers),
}

impl UnavailableVersion {
Expand All @@ -54,7 +54,9 @@ impl UnavailableVersion {
UnavailableVersion::InconsistentMetadata => "inconsistent metadata".into(),
UnavailableVersion::InvalidStructure => "an invalid package format".into(),
UnavailableVersion::Offline => "to be downloaded from a registry".into(),
UnavailableVersion::RequiresPython => "require a greater Python version".into(),
UnavailableVersion::RequiresPython(requires_python) => {
format!("Python {requires_python}")
}
}
}

Expand All @@ -66,7 +68,7 @@ impl UnavailableVersion {
UnavailableVersion::InconsistentMetadata => format!("has {self}"),
UnavailableVersion::InvalidStructure => format!("has {self}"),
UnavailableVersion::Offline => format!("needs {self}"),
UnavailableVersion::RequiresPython => format!("requires {self}"),
UnavailableVersion::RequiresPython(..) => format!("requires {self}"),
}
}

Expand All @@ -78,7 +80,7 @@ impl UnavailableVersion {
UnavailableVersion::InconsistentMetadata => format!("have {self}"),
UnavailableVersion::InvalidStructure => format!("have {self}"),
UnavailableVersion::Offline => format!("need {self}"),
UnavailableVersion::RequiresPython => format!("require {self}"),
UnavailableVersion::RequiresPython(..) => format!("require {self}"),
}
}
}
Expand Down Expand Up @@ -151,7 +153,7 @@ pub(crate) enum IncompletePackage {
InvalidStructure(String),
/// The source distribution has a `requires-python` requirement that is not met by the installed
/// Python version (and static metadata is not available).
RequiresPython(String),
RequiresPython(VersionSpecifiers, Version),
}

#[derive(Debug, Clone)]
Expand Down
156 changes: 68 additions & 88 deletions crates/uv-resolver/src/resolver/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -958,8 +958,8 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
);
return Ok(None);
}
MetadataResponse::RequiresPython(_) => {
unreachable!("`requires-python` is not known upfront for URL requirements")
MetadataResponse::RequiresPython(..) => {
unreachable!("`requires-python` is only known upfront for registry distributions")
}
};

Expand Down Expand Up @@ -1077,72 +1077,54 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
}
};

let incompatibility = match dist {
// Validate the Python requirement.
let requires_python = match dist {
CompatibleDist::InstalledDist(_) => None,
CompatibleDist::SourceDist { sdist, .. }
| CompatibleDist::IncompatibleWheel { sdist, .. } => {
// Source distributions must meet both the _target_ Python version and the
// _installed_ Python version (to build successfully).
sdist
.file
.requires_python
.as_ref()
.and_then(|requires_python| {
// if !python_requirement
// .installed()
// .is_contained_by(requires_python)
// {
// return Some(IncompatibleDist::Source(
// IncompatibleSource::RequiresPython(
// requires_python.clone(),
// PythonRequirementKind::Installed,
// ),
// ));
// }
if !python_requirement.target().is_contained_by(requires_python) {
return Some(IncompatibleDist::Source(
IncompatibleSource::RequiresPython(
requires_python.clone(),
PythonRequirementKind::Target,
),
));
}
None
})
}
CompatibleDist::CompatibleWheel { wheel, .. } => {
// Wheels must meet the _target_ Python version.
wheel
.file
.requires_python
.as_ref()
.and_then(|requires_python| {
if python_requirement.installed() == python_requirement.target() {
if !python_requirement
.installed()
.is_contained_by(requires_python)
{
return Some(IncompatibleDist::Wheel(
IncompatibleWheel::RequiresPython(
requires_python.clone(),
PythonRequirementKind::Installed,
),
));
}
} else {
if !python_requirement.target().is_contained_by(requires_python) {
return Some(IncompatibleDist::Wheel(
IncompatibleWheel::RequiresPython(
requires_python.clone(),
PythonRequirementKind::Target,
),
));
}
}
None
})
sdist.file.requires_python.as_ref()
}
CompatibleDist::CompatibleWheel { wheel, .. } => wheel.file.requires_python.as_ref(),
};
let incompatibility = requires_python.and_then(|requires_python| {
if python_requirement.installed() == python_requirement.target() {
if !python_requirement
.installed()
.is_contained_by(requires_python)
{
return if matches!(dist, CompatibleDist::CompatibleWheel { .. }) {
Some(IncompatibleDist::Wheel(IncompatibleWheel::RequiresPython(
requires_python.clone(),
PythonRequirementKind::Installed,
)))
} else {
Some(IncompatibleDist::Source(
IncompatibleSource::RequiresPython(
requires_python.clone(),
PythonRequirementKind::Installed,
),
))
};
}
} else {
if !python_requirement.target().is_contained_by(requires_python) {
return if matches!(dist, CompatibleDist::CompatibleWheel { .. }) {
Some(IncompatibleDist::Wheel(IncompatibleWheel::RequiresPython(
requires_python.clone(),
PythonRequirementKind::Target,
)))
} else {
Some(IncompatibleDist::Source(
IncompatibleSource::RequiresPython(
requires_python.clone(),
PythonRequirementKind::Target,
),
))
};
}
}
None
});

// The version is incompatible due to its Python requirement.
if let Some(incompatibility) = incompatibility {
Expand Down Expand Up @@ -1345,17 +1327,26 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
UnavailableVersion::InvalidStructure,
));
}
MetadataResponse::RequiresPython(err) => {
warn!("Unable to extract metadata for {name}: {err}");
MetadataResponse::RequiresPython(requires_python, python_version) => {
warn!(
"Unable to extract metadata for {name}: {}",
uv_distribution::Error::RequiresPython(
requires_python.clone(),
python_version.clone()
)
);
self.incomplete_packages
.entry(name.clone())
.or_default()
.insert(
version.clone(),
IncompletePackage::RequiresPython(err.to_string()),
IncompletePackage::RequiresPython(
requires_python.clone(),
python_version.clone(),
),
);
return Ok(Dependencies::Unavailable(
UnavailableVersion::RequiresPython,
UnavailableVersion::RequiresPython(requires_python.clone()),
));
}
};
Expand Down Expand Up @@ -1904,33 +1895,22 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
}
}

match dist {
CompatibleDist::InstalledDist(_) => {}
// Validate the Python requirement.
let requires_python = match dist {
CompatibleDist::InstalledDist(_) => None,
CompatibleDist::SourceDist { sdist, .. }
| CompatibleDist::IncompatibleWheel { sdist, .. } => {
// Source distributions must meet both the _target_ Python version and the
// _installed_ Python version (to build successfully).
if let Some(requires_python) = sdist.file.requires_python.as_ref() {
// if !python_requirement
// .installed()
// .is_contained_by(requires_python)
// {
// return Ok(None);
// }
if !python_requirement.target().is_contained_by(requires_python) {
return Ok(None);
}
}
sdist.file.requires_python.as_ref()
}
CompatibleDist::CompatibleWheel { wheel, .. } => {
// Wheels must meet the _target_ Python version.
if let Some(requires_python) = wheel.file.requires_python.as_ref() {
if !python_requirement.target().is_contained_by(requires_python) {
return Ok(None);
}
}
wheel.file.requires_python.as_ref()
}
};
if let Some(requires_python) = requires_python.as_ref() {
if !python_requirement.target().is_contained_by(requires_python) {
return Ok(None);
}
}

// Emit a request to fetch the metadata for this version.
if self.index.distributions().register(candidate.version_id()) {
Expand Down
7 changes: 4 additions & 3 deletions crates/uv-resolver/src/resolver/provider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use uv_configuration::BuildOptions;
use uv_distribution::{ArchiveMetadata, DistributionDatabase};
use uv_distribution_types::{Dist, IndexCapabilities, IndexUrl};
use uv_normalize::PackageName;
use uv_pep440::{Version, VersionSpecifiers};
use uv_platform_tags::Tags;
use uv_types::{BuildContext, HashStrategy};

Expand Down Expand Up @@ -44,7 +45,7 @@ pub enum MetadataResponse {
Offline,
/// The source distribution has a `requires-python` requirement that is not met by the installed
/// Python version (and static metadata is not available).
RequiresPython(Box<uv_distribution::Error>),
RequiresPython(VersionSpecifiers, Version),
}

pub trait ResolverProvider {
Expand Down Expand Up @@ -206,8 +207,8 @@ impl<'a, Context: BuildContext> ResolverProvider for DefaultResolverProvider<'a,
uv_distribution::Error::WheelMetadata(_, err) => {
Ok(MetadataResponse::InvalidStructure(err))
}
uv_distribution::Error::RequiresPython { .. } => {
Ok(MetadataResponse::RequiresPython(Box::new(err)))
uv_distribution::Error::RequiresPython(requires_python, version) => {
Ok(MetadataResponse::RequiresPython(requires_python, version))
}
err => Err(err),
},
Expand Down
Loading

0 comments on commit d24c42c

Please sign in to comment.