Always narrow markers by Python version (#6076)
## Summary Using https://github.com/astral-sh/uv/issues/6064 as a motivating example: at present, on main, we're not properly propagating the `Requires-Python` simplifications. In that case, for example, we end up solving for a branch with `python_version < 3.11`, and a branch `>= 3.11`, even though `Requires-Python` is `>=3.11`. Later, when we get to the graph, we apply version simplification based on `Requires-Python`, which causes us to _remove_ the `python_version < 3.11` markers entirely, leaving us with duplicate dependencies for `pylint`. This PR instead tries to ensure that we always apply this narrowing to requirements and forks, so that we don't need to apply the same simplification when constructing the graph at all. Closes https://github.com/astral-sh/uv/issues/6064. Closes #6059.
This commit is contained in:
@@ -6,8 +6,7 @@ use itertools::Itertools;
|
||||
use pubgrub::Range;
|
||||
|
||||
use distribution_filename::WheelFilename;
|
||||
use pep440_rs::{Operator, Version, VersionSpecifier, VersionSpecifiers};
|
||||
use pep508_rs::{MarkerExpression, MarkerTree, MarkerValueVersion};
|
||||
use pep440_rs::{Version, VersionSpecifier, VersionSpecifiers};
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum RequiresPythonError {
|
||||
@@ -196,65 +195,6 @@ impl RequiresPython {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns this `Requires-Python` specifier as an equivalent marker
|
||||
/// expression utilizing the `python_version` marker field.
|
||||
///
|
||||
/// This is useful for comparing a `Requires-Python` specifier with
|
||||
/// arbitrary marker expressions. For example, one can ask whether the
|
||||
/// returned marker expression is disjoint with another marker expression.
|
||||
/// If it is, then one can conclude that the `Requires-Python` specifier
|
||||
/// excludes the dependency with that other marker expression.
|
||||
///
|
||||
/// If this `Requires-Python` specifier has no constraints, then this
|
||||
/// returns a marker tree that evaluates to `true` for all possible marker
|
||||
/// environments.
|
||||
pub fn to_marker_tree(&self) -> MarkerTree {
|
||||
let (op, version) = match self.bound.as_ref() {
|
||||
// If we see this anywhere, then it implies the marker
|
||||
// tree we would generate would always evaluate to
|
||||
// `true` because every possible Python version would
|
||||
// satisfy it.
|
||||
Bound::Unbounded => return MarkerTree::TRUE,
|
||||
Bound::Excluded(version) => (Operator::GreaterThan, version.clone().without_local()),
|
||||
Bound::Included(version) => {
|
||||
(Operator::GreaterThanEqual, version.clone().without_local())
|
||||
}
|
||||
};
|
||||
// For the `python_version` marker expression, it specifically only
|
||||
// supports truncate major/minor versions of Python. This means that
|
||||
// a `Requires-Python: 3.10.1` is satisfied by `python_version ==
|
||||
// '3.10'`. So for disjointness checking, we need to ensure that the
|
||||
// marker expression we generate for `Requires-Python` doesn't try to
|
||||
// be overly selective about the patch version. We do this by keeping
|
||||
// this part of our marker limited to the major and minor version
|
||||
// components only.
|
||||
let version_major_minor_only = Version::new(version.release().iter().take(2));
|
||||
let expr_python_version = MarkerExpression::Version {
|
||||
key: MarkerValueVersion::PythonVersion,
|
||||
// OK because a version specifier is only invalid when the
|
||||
// version is local (which is impossible here because we
|
||||
// strip it above) or if the operator is ~= (which is also
|
||||
// impossible here).
|
||||
specifier: VersionSpecifier::from_version(op, version_major_minor_only).unwrap(),
|
||||
};
|
||||
let expr_python_full_version = MarkerExpression::Version {
|
||||
key: MarkerValueVersion::PythonFullVersion,
|
||||
// For `python_full_version`, we can use the entire
|
||||
// version as-is.
|
||||
//
|
||||
// OK because a version specifier is only invalid when the
|
||||
// version is local (which is impossible here because we
|
||||
// strip it above) or if the operator is ~= (which is also
|
||||
// impossible here).
|
||||
specifier: VersionSpecifier::from_version(op, version).unwrap(),
|
||||
};
|
||||
|
||||
let mut conjunction = MarkerTree::TRUE;
|
||||
conjunction.and(MarkerTree::expression(expr_python_version));
|
||||
conjunction.and(MarkerTree::expression(expr_python_full_version));
|
||||
conjunction
|
||||
}
|
||||
|
||||
/// Returns `false` if the wheel's tags state it can't be used in the given Python version
|
||||
/// range.
|
||||
///
|
||||
|
||||
Reference in New Issue
Block a user