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 dd9d8ca
Show file tree
Hide file tree
Showing 8 changed files with 174 additions and 110 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 contain static metadata, but generating metadata for this package requires Python {}, which is not met by the installed Python version ({}).",
"hint".bold().cyan(),
":".bold(),
package.bold(),
version.bold(),
requires_python.bold(),
python_version.bold(),
)
}
Self::RequiresPython {
source: PythonRequirementSource::Interpreter,
requires_python: _,
Expand Down
4 changes: 2 additions & 2 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 @@ -151,7 +151,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
130 changes: 63 additions & 67 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 @@ -1080,25 +1080,24 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
let incompatibility = 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,
// ),
// ));
// }
| CompatibleDist::IncompatibleWheel { sdist, .. } => sdist
.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::Source(
IncompatibleSource::RequiresPython(
requires_python.clone(),
PythonRequirementKind::Installed,
),
));
}
} else {
if !python_requirement.target().is_contained_by(requires_python) {
return Some(IncompatibleDist::Source(
IncompatibleSource::RequiresPython(
Expand All @@ -1107,41 +1106,38 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
),
));
}
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
}),
CompatibleDist::CompatibleWheel { wheel, .. } => 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,
),
));
}
None
})
}
} else {
if !python_requirement.target().is_contained_by(requires_python) {
return Some(IncompatibleDist::Wheel(
IncompatibleWheel::RequiresPython(
requires_python.clone(),
PythonRequirementKind::Target,
),
));
}
}
None
}),
};

// The version is incompatible due to its Python requirement.
Expand Down Expand Up @@ -1345,14 +1341,23 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
UnavailableVersion::InvalidStructure,
));
}
MetadataResponse::RequiresPython(err) => {
warn!("Unable to extract metadata for {name}: {err}");
MetadataResponse::RequiresPython(requires_python, version) => {
warn!(
"Unable to extract metadata for {name}: {}",
uv_distribution::Error::RequiresPython(
requires_python.clone(),
version.clone()
)
);
self.incomplete_packages
.entry(name.clone())
.or_default()
.insert(
version.clone(),
IncompletePackage::RequiresPython(err.to_string()),
IncompletePackage::RequiresPython(
requires_python.clone(),
version.clone(),
),
);
return Ok(Dependencies::Unavailable(
UnavailableVersion::RequiresPython,
Expand Down Expand Up @@ -1908,22 +1913,13 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
CompatibleDist::InstalledDist(_) => {}
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);
}
}
}
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);
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
30 changes: 30 additions & 0 deletions crates/uv/tests/it/pip_compile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12661,3 +12661,33 @@ fn prune_unreachable() -> Result<()> {

Ok(())
}

/// Allow resolving a package that requires a Python version that is not available, as long as it
/// includes static metadata.
///
/// See: <https://github.com/astral-sh/uv/issues/8767>
#[test]
fn unsupported_requires_python_static_metadata() -> Result<()> {
let context = TestContext::new("3.11");
let requirements_in = context.temp_dir.child("requirements.in");
requirements_in.write_str("interpreters-pep-734 <= 0.4.1 ; python_version >= '3.13'")?;

uv_snapshot!(context
.pip_compile()
.arg("--universal")
.arg("requirements.in")
.env(EnvVars::UV_EXCLUDE_NEWER, "2024-11-02T00:00:00Z"), @r###"
success: true
exit_code: 0
----- stdout -----
# This file was autogenerated by uv via the following command:
# uv pip compile --cache-dir [CACHE_DIR] --universal requirements.in
interpreters-pep-734==0.4.1 ; python_full_version >= '3.13'
# via -r requirements.in
----- stderr -----
Resolved 1 package in [TIME]
"###);

Ok(())
}
Loading

0 comments on commit dd9d8ca

Please sign in to comment.