Report incompatible distributions to users (#1293)
Instead of dropping versions without a compatible distribution, we track them as incompatibilities in the solver. This implementation follows patterns established in https://github.com/astral-sh/puffin/pull/1290. This required some significant refactoring of how we track incompatible distributions. Notably: - `Option<TagPriority>` is now `WheelCompatibility` which allows us to track the reason a wheel is incompatible instead of just `None`. - `Candidate` now has a `CandidateDist` with `Compatible` and `Incompatibile` variants instead of just `ResolvableDist`; candidates are not strictly compatible anymore - `ResolvableDist` was renamed to `CompatibleDist` - `IncompatibleWheel` was given an ordering implementation so we can track the "most compatible" (but still incompatible) wheel. This allows us to collapse the reason a version cannot be used to a single incompatibility. - The filtering in the `VersionMap` is retained, we still only store one incompatible wheel per version. This is sufficient for error reporting. - A `TagCompatibility` type was added for tracking which part of a wheel tag is incompatible - `Candidate::validate_python` moved to `PythonRequirement::validate_dist` I am doing more refactoring in #1298 — I think a couple passes will be necessary to clarify the relationships of these types. Includes improved error message snapshots for multiple incompatible Python tag types from #1285 — we should add more scenarios for coverage of behavior when multiple tags with different levels are present.
This commit is contained in:
@@ -1,8 +1,9 @@
|
||||
use pep440_rs::Version;
|
||||
use distribution_types::{CompatibleDist, Dist};
|
||||
use pep440_rs::{Version, VersionSpecifiers};
|
||||
use pep508_rs::MarkerEnvironment;
|
||||
use puffin_interpreter::Interpreter;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd)]
|
||||
pub struct PythonRequirement {
|
||||
/// The installed version of Python.
|
||||
installed: Version,
|
||||
@@ -29,4 +30,42 @@ impl PythonRequirement {
|
||||
pub(crate) fn target(&self) -> &Version {
|
||||
&self.target
|
||||
}
|
||||
|
||||
/// If the dist doesn't match the given Python requirement, return the version specifiers.
|
||||
pub(crate) fn validate_dist<'a>(
|
||||
&self,
|
||||
dist: &'a CompatibleDist,
|
||||
) -> Option<&'a VersionSpecifiers> {
|
||||
// Validate the _installed_ file.
|
||||
let requires_python = dist.for_installation().requires_python.as_ref()?;
|
||||
|
||||
// If the dist doesn't support the target Python version, return the failing version
|
||||
// specifiers.
|
||||
if !requires_python.contains(self.target()) {
|
||||
return Some(requires_python);
|
||||
}
|
||||
|
||||
// If the dist is a source distribution, and doesn't support the installed Python
|
||||
// version, return the failing version specifiers, since we won't be able to build it.
|
||||
if matches!(dist.for_installation().dist, Dist::Source(_)) {
|
||||
if !requires_python.contains(self.installed()) {
|
||||
return Some(requires_python);
|
||||
}
|
||||
}
|
||||
|
||||
// Validate the resolved file.
|
||||
let requires_python = dist.for_resolution().requires_python.as_ref()?;
|
||||
|
||||
// If the dist is a source distribution, and doesn't support the installed Python
|
||||
// version, return the failing version specifiers, since we won't be able to build it.
|
||||
// This isn't strictly necessary, since if `dist.resolve_metadata()` is a source distribution, it
|
||||
// should be the same file as `dist.install_metadata()` (validated above).
|
||||
if matches!(dist.for_resolution().dist, Dist::Source(_)) {
|
||||
if !requires_python.contains(self.installed()) {
|
||||
return Some(requires_python);
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user