Skip to content

Commit d24c42c

Browse files
committed
Add failure hint
1 parent 7a9a9f5 commit d24c42c

File tree

8 files changed

+214
-135
lines changed

8 files changed

+214
-135
lines changed

crates/uv-distribution/src/error.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,8 +96,7 @@ pub enum Error {
9696
NotFound(Url),
9797
#[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())]
9898
CacheHeal(String),
99-
100-
#[error("The source distribution requires Python {0}, but the current Python version is {1}")]
99+
#[error("The source distribution requires Python {0}, but {1} is installed")]
101100
RequiresPython(VersionSpecifiers, Version),
102101

103102
/// A generic request middleware error happened while making a request.

crates/uv-distribution/src/source/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ use uv_distribution_types::{
3434
use uv_extract::hash::Hasher;
3535
use uv_fs::{rename_with_retry, write_atomic, LockedFile};
3636
use uv_metadata::read_archive_metadata;
37-
use uv_pep440::{release_specifiers_to_ranges, Version};
37+
use uv_pep440::release_specifiers_to_ranges;
3838
use uv_platform_tags::Tags;
3939
use uv_pypi_types::{HashDigest, Metadata12, RequiresTxt, ResolutionMetadata};
4040
use uv_types::{BuildContext, SourceBuildTrait};

crates/uv-resolver/src/pubgrub/report.rs

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use rustc_hash::FxHashMap;
1111
use uv_configuration::IndexStrategy;
1212
use uv_distribution_types::{Index, IndexCapabilities, IndexLocations, IndexUrl};
1313
use uv_normalize::PackageName;
14-
use uv_pep440::Version;
14+
use uv_pep440::{Version, VersionSpecifiers};
1515

1616
use crate::candidate_selector::CandidateSelector;
1717
use crate::error::ErrorTree;
@@ -699,7 +699,14 @@ impl PubGrubReportFormatter<'_> {
699699
reason: reason.clone(),
700700
});
701701
}
702-
IncompletePackage::RequiresPython(_) => {}
702+
IncompletePackage::RequiresPython(requires_python, python_version) => {
703+
hints.insert(PubGrubHint::IncompatibleBuildRequirement {
704+
package: package.clone(),
705+
version: version.clone(),
706+
requires_python: requires_python.clone(),
707+
python_version: python_version.clone(),
708+
});
709+
}
703710
}
704711
break;
705712
}
@@ -863,6 +870,17 @@ pub(crate) enum PubGrubHint {
863870
// excluded from `PartialEq` and `Hash`
864871
reason: String,
865872
},
873+
/// The source distribution has a `requires-python` requirement that is not met by the installed
874+
/// Python version (and static metadata is not available).
875+
IncompatibleBuildRequirement {
876+
package: PubGrubPackage,
877+
// excluded from `PartialEq` and `Hash`
878+
version: Version,
879+
// excluded from `PartialEq` and `Hash`
880+
requires_python: VersionSpecifiers,
881+
// excluded from `PartialEq` and `Hash`
882+
python_version: Version,
883+
},
866884
/// The `Requires-Python` requirement was not satisfied.
867885
RequiresPython {
868886
source: PythonRequirementSource,
@@ -933,6 +951,9 @@ enum PubGrubHintCore {
933951
InvalidVersionStructure {
934952
package: PubGrubPackage,
935953
},
954+
IncompatibleBuildRequirement {
955+
package: PubGrubPackage,
956+
},
936957
RequiresPython {
937958
source: PythonRequirementSource,
938959
requires_python: RequiresPython,
@@ -986,6 +1007,9 @@ impl From<PubGrubHint> for PubGrubHintCore {
9861007
PubGrubHint::InvalidVersionStructure { package, .. } => {
9871008
Self::InvalidVersionStructure { package }
9881009
}
1010+
PubGrubHint::IncompatibleBuildRequirement { package, .. } => {
1011+
Self::IncompatibleBuildRequirement { package }
1012+
}
9891013
PubGrubHint::RequiresPython {
9901014
source,
9911015
requires_python,
@@ -1188,6 +1212,23 @@ impl std::fmt::Display for PubGrubHint {
11881212
package_requires_python.bold(),
11891213
)
11901214
}
1215+
Self::IncompatibleBuildRequirement {
1216+
package,
1217+
version,
1218+
requires_python,
1219+
python_version,
1220+
} => {
1221+
write!(
1222+
f,
1223+
"{}{} The source distribution for {}=={} does not include static metadata. Generating metadata for this package requires Python {}, but Python {} is installed.",
1224+
"hint".bold().cyan(),
1225+
":".bold(),
1226+
package.bold(),
1227+
version.bold(),
1228+
requires_python.bold(),
1229+
python_version.bold(),
1230+
)
1231+
}
11911232
Self::RequiresPython {
11921233
source: PythonRequirementSource::Interpreter,
11931234
requires_python: _,

crates/uv-resolver/src/resolver/availability.rs

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use std::fmt::{Display, Formatter};
22

33
use uv_distribution_types::IncompatibleDist;
4-
use uv_pep440::Version;
4+
use uv_pep440::{Version, VersionSpecifiers};
55

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

4848
impl UnavailableVersion {
@@ -54,7 +54,9 @@ impl UnavailableVersion {
5454
UnavailableVersion::InconsistentMetadata => "inconsistent metadata".into(),
5555
UnavailableVersion::InvalidStructure => "an invalid package format".into(),
5656
UnavailableVersion::Offline => "to be downloaded from a registry".into(),
57-
UnavailableVersion::RequiresPython => "require a greater Python version".into(),
57+
UnavailableVersion::RequiresPython(requires_python) => {
58+
format!("Python {requires_python}")
59+
}
5860
}
5961
}
6062

@@ -66,7 +68,7 @@ impl UnavailableVersion {
6668
UnavailableVersion::InconsistentMetadata => format!("has {self}"),
6769
UnavailableVersion::InvalidStructure => format!("has {self}"),
6870
UnavailableVersion::Offline => format!("needs {self}"),
69-
UnavailableVersion::RequiresPython => format!("requires {self}"),
71+
UnavailableVersion::RequiresPython(..) => format!("requires {self}"),
7072
}
7173
}
7274

@@ -78,7 +80,7 @@ impl UnavailableVersion {
7880
UnavailableVersion::InconsistentMetadata => format!("have {self}"),
7981
UnavailableVersion::InvalidStructure => format!("have {self}"),
8082
UnavailableVersion::Offline => format!("need {self}"),
81-
UnavailableVersion::RequiresPython => format!("require {self}"),
83+
UnavailableVersion::RequiresPython(..) => format!("require {self}"),
8284
}
8385
}
8486
}
@@ -151,7 +153,7 @@ pub(crate) enum IncompletePackage {
151153
InvalidStructure(String),
152154
/// The source distribution has a `requires-python` requirement that is not met by the installed
153155
/// Python version (and static metadata is not available).
154-
RequiresPython(String),
156+
RequiresPython(VersionSpecifiers, Version),
155157
}
156158

157159
#[derive(Debug, Clone)]

crates/uv-resolver/src/resolver/mod.rs

Lines changed: 68 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -958,8 +958,8 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
958958
);
959959
return Ok(None);
960960
}
961-
MetadataResponse::RequiresPython(_) => {
962-
unreachable!("`requires-python` is not known upfront for URL requirements")
961+
MetadataResponse::RequiresPython(..) => {
962+
unreachable!("`requires-python` is only known upfront for registry distributions")
963963
}
964964
};
965965

@@ -1077,72 +1077,54 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
10771077
}
10781078
};
10791079

1080-
let incompatibility = match dist {
1080+
// Validate the Python requirement.
1081+
let requires_python = match dist {
10811082
CompatibleDist::InstalledDist(_) => None,
10821083
CompatibleDist::SourceDist { sdist, .. }
10831084
| CompatibleDist::IncompatibleWheel { sdist, .. } => {
1084-
// Source distributions must meet both the _target_ Python version and the
1085-
// _installed_ Python version (to build successfully).
1086-
sdist
1087-
.file
1088-
.requires_python
1089-
.as_ref()
1090-
.and_then(|requires_python| {
1091-
// if !python_requirement
1092-
// .installed()
1093-
// .is_contained_by(requires_python)
1094-
// {
1095-
// return Some(IncompatibleDist::Source(
1096-
// IncompatibleSource::RequiresPython(
1097-
// requires_python.clone(),
1098-
// PythonRequirementKind::Installed,
1099-
// ),
1100-
// ));
1101-
// }
1102-
if !python_requirement.target().is_contained_by(requires_python) {
1103-
return Some(IncompatibleDist::Source(
1104-
IncompatibleSource::RequiresPython(
1105-
requires_python.clone(),
1106-
PythonRequirementKind::Target,
1107-
),
1108-
));
1109-
}
1110-
None
1111-
})
1112-
}
1113-
CompatibleDist::CompatibleWheel { wheel, .. } => {
1114-
// Wheels must meet the _target_ Python version.
1115-
wheel
1116-
.file
1117-
.requires_python
1118-
.as_ref()
1119-
.and_then(|requires_python| {
1120-
if python_requirement.installed() == python_requirement.target() {
1121-
if !python_requirement
1122-
.installed()
1123-
.is_contained_by(requires_python)
1124-
{
1125-
return Some(IncompatibleDist::Wheel(
1126-
IncompatibleWheel::RequiresPython(
1127-
requires_python.clone(),
1128-
PythonRequirementKind::Installed,
1129-
),
1130-
));
1131-
}
1132-
} else {
1133-
if !python_requirement.target().is_contained_by(requires_python) {
1134-
return Some(IncompatibleDist::Wheel(
1135-
IncompatibleWheel::RequiresPython(
1136-
requires_python.clone(),
1137-
PythonRequirementKind::Target,
1138-
),
1139-
));
1140-
}
1141-
}
1142-
None
1143-
})
1085+
sdist.file.requires_python.as_ref()
11441086
}
1087+
CompatibleDist::CompatibleWheel { wheel, .. } => wheel.file.requires_python.as_ref(),
11451088
};
1089+
let incompatibility = requires_python.and_then(|requires_python| {
1090+
if python_requirement.installed() == python_requirement.target() {
1091+
if !python_requirement
1092+
.installed()
1093+
.is_contained_by(requires_python)
1094+
{
1095+
return if matches!(dist, CompatibleDist::CompatibleWheel { .. }) {
1096+
Some(IncompatibleDist::Wheel(IncompatibleWheel::RequiresPython(
1097+
requires_python.clone(),
1098+
PythonRequirementKind::Installed,
1099+
)))
1100+
} else {
1101+
Some(IncompatibleDist::Source(
1102+
IncompatibleSource::RequiresPython(
1103+
requires_python.clone(),
1104+
PythonRequirementKind::Installed,
1105+
),
1106+
))
1107+
};
1108+
}
1109+
} else {
1110+
if !python_requirement.target().is_contained_by(requires_python) {
1111+
return if matches!(dist, CompatibleDist::CompatibleWheel { .. }) {
1112+
Some(IncompatibleDist::Wheel(IncompatibleWheel::RequiresPython(
1113+
requires_python.clone(),
1114+
PythonRequirementKind::Target,
1115+
)))
1116+
} else {
1117+
Some(IncompatibleDist::Source(
1118+
IncompatibleSource::RequiresPython(
1119+
requires_python.clone(),
1120+
PythonRequirementKind::Target,
1121+
),
1122+
))
1123+
};
1124+
}
1125+
}
1126+
None
1127+
});
11461128

11471129
// The version is incompatible due to its Python requirement.
11481130
if let Some(incompatibility) = incompatibility {
@@ -1345,17 +1327,26 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
13451327
UnavailableVersion::InvalidStructure,
13461328
));
13471329
}
1348-
MetadataResponse::RequiresPython(err) => {
1349-
warn!("Unable to extract metadata for {name}: {err}");
1330+
MetadataResponse::RequiresPython(requires_python, python_version) => {
1331+
warn!(
1332+
"Unable to extract metadata for {name}: {}",
1333+
uv_distribution::Error::RequiresPython(
1334+
requires_python.clone(),
1335+
python_version.clone()
1336+
)
1337+
);
13501338
self.incomplete_packages
13511339
.entry(name.clone())
13521340
.or_default()
13531341
.insert(
13541342
version.clone(),
1355-
IncompletePackage::RequiresPython(err.to_string()),
1343+
IncompletePackage::RequiresPython(
1344+
requires_python.clone(),
1345+
python_version.clone(),
1346+
),
13561347
);
13571348
return Ok(Dependencies::Unavailable(
1358-
UnavailableVersion::RequiresPython,
1349+
UnavailableVersion::RequiresPython(requires_python.clone()),
13591350
));
13601351
}
13611352
};
@@ -1904,33 +1895,22 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
19041895
}
19051896
}
19061897

1907-
match dist {
1908-
CompatibleDist::InstalledDist(_) => {}
1898+
// Validate the Python requirement.
1899+
let requires_python = match dist {
1900+
CompatibleDist::InstalledDist(_) => None,
19091901
CompatibleDist::SourceDist { sdist, .. }
19101902
| CompatibleDist::IncompatibleWheel { sdist, .. } => {
1911-
// Source distributions must meet both the _target_ Python version and the
1912-
// _installed_ Python version (to build successfully).
1913-
if let Some(requires_python) = sdist.file.requires_python.as_ref() {
1914-
// if !python_requirement
1915-
// .installed()
1916-
// .is_contained_by(requires_python)
1917-
// {
1918-
// return Ok(None);
1919-
// }
1920-
if !python_requirement.target().is_contained_by(requires_python) {
1921-
return Ok(None);
1922-
}
1923-
}
1903+
sdist.file.requires_python.as_ref()
19241904
}
19251905
CompatibleDist::CompatibleWheel { wheel, .. } => {
1926-
// Wheels must meet the _target_ Python version.
1927-
if let Some(requires_python) = wheel.file.requires_python.as_ref() {
1928-
if !python_requirement.target().is_contained_by(requires_python) {
1929-
return Ok(None);
1930-
}
1931-
}
1906+
wheel.file.requires_python.as_ref()
19321907
}
19331908
};
1909+
if let Some(requires_python) = requires_python.as_ref() {
1910+
if !python_requirement.target().is_contained_by(requires_python) {
1911+
return Ok(None);
1912+
}
1913+
}
19341914

19351915
// Emit a request to fetch the metadata for this version.
19361916
if self.index.distributions().register(candidate.version_id()) {

crates/uv-resolver/src/resolver/provider.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use uv_configuration::BuildOptions;
44
use uv_distribution::{ArchiveMetadata, DistributionDatabase};
55
use uv_distribution_types::{Dist, IndexCapabilities, IndexUrl};
66
use uv_normalize::PackageName;
7+
use uv_pep440::{Version, VersionSpecifiers};
78
use uv_platform_tags::Tags;
89
use uv_types::{BuildContext, HashStrategy};
910

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

5051
pub trait ResolverProvider {
@@ -206,8 +207,8 @@ impl<'a, Context: BuildContext> ResolverProvider for DefaultResolverProvider<'a,
206207
uv_distribution::Error::WheelMetadata(_, err) => {
207208
Ok(MetadataResponse::InvalidStructure(err))
208209
}
209-
uv_distribution::Error::RequiresPython { .. } => {
210-
Ok(MetadataResponse::RequiresPython(Box::new(err)))
210+
uv_distribution::Error::RequiresPython(requires_python, version) => {
211+
Ok(MetadataResponse::RequiresPython(requires_python, version))
211212
}
212213
err => Err(err),
213214
},

0 commit comments

Comments
 (0)