mirror of
https://github.com/astral-sh/uv.git
synced 2025-11-19 11:35:36 +00:00
Normalize extras when evaluating PEP 508 markers (#915)
## Summary We always normalize extra names in our requirements (e.g., `cuda12_pip` to `cuda12-pip`), but we weren't normalizing within PEP 508 markers, which meant we ended up comparing `cuda12-pip` (normalized) against `cuda12_pip` (unnormalized). Closes https://github.com/astral-sh/puffin/issues/911.
This commit is contained in:
parent
a99e5e00f2
commit
0374000ec0
3 changed files with 84 additions and 52 deletions
|
|
@ -41,6 +41,8 @@ pub use marker::{
|
||||||
MarkerValueString, MarkerValueVersion, MarkerWarningKind, StringVersion,
|
MarkerValueString, MarkerValueVersion, MarkerWarningKind, StringVersion,
|
||||||
};
|
};
|
||||||
use pep440_rs::{Version, VersionSpecifier, VersionSpecifiers};
|
use pep440_rs::{Version, VersionSpecifier, VersionSpecifiers};
|
||||||
|
#[cfg(feature = "pyo3")]
|
||||||
|
use puffin_normalize::InvalidNameError;
|
||||||
use puffin_normalize::{ExtraName, PackageName};
|
use puffin_normalize::{ExtraName, PackageName};
|
||||||
pub use verbatim_url::VerbatimUrl;
|
pub use verbatim_url::VerbatimUrl;
|
||||||
|
|
||||||
|
|
@ -192,6 +194,8 @@ impl Serialize for Requirement {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type MarkerWarning = (MarkerWarningKind, String, String);
|
||||||
|
|
||||||
#[cfg(feature = "pyo3")]
|
#[cfg(feature = "pyo3")]
|
||||||
#[pymethods]
|
#[pymethods]
|
||||||
impl Requirement {
|
impl Requirement {
|
||||||
|
|
@ -266,11 +270,18 @@ impl Requirement {
|
||||||
/// Returns whether the markers apply for the given environment
|
/// Returns whether the markers apply for the given environment
|
||||||
#[allow(clippy::needless_pass_by_value)]
|
#[allow(clippy::needless_pass_by_value)]
|
||||||
#[pyo3(name = "evaluate_markers")]
|
#[pyo3(name = "evaluate_markers")]
|
||||||
pub fn py_evaluate_markers(&self, env: &MarkerEnvironment, extras: Vec<String>) -> bool {
|
pub fn py_evaluate_markers(
|
||||||
self.evaluate_markers(
|
&self,
|
||||||
env,
|
env: &MarkerEnvironment,
|
||||||
&extras.iter().map(String::as_str).collect::<Vec<&str>>(),
|
extras: Vec<String>,
|
||||||
)
|
) -> PyResult<bool> {
|
||||||
|
let extras = extras
|
||||||
|
.into_iter()
|
||||||
|
.map(|extra| ExtraName::from_str(&extra))
|
||||||
|
.collect::<Result<Vec<_>, InvalidNameError>>()
|
||||||
|
.map_err(|err| PyPep508Error::new_err(err.to_string()))?;
|
||||||
|
|
||||||
|
Ok(self.evaluate_markers(env, &extras))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns whether the requirement would be satisfied, independent of environment markers, i.e.
|
/// Returns whether the requirement would be satisfied, independent of environment markers, i.e.
|
||||||
|
|
@ -285,14 +296,19 @@ impl Requirement {
|
||||||
&self,
|
&self,
|
||||||
extras: HashSet<String>,
|
extras: HashSet<String>,
|
||||||
python_versions: Vec<PyVersion>,
|
python_versions: Vec<PyVersion>,
|
||||||
) -> bool {
|
) -> PyResult<bool> {
|
||||||
self.evaluate_extras_and_python_version(
|
let extras = extras
|
||||||
&extras,
|
.into_iter()
|
||||||
&python_versions
|
.map(|extra| ExtraName::from_str(&extra))
|
||||||
|
.collect::<Result<HashSet<_>, InvalidNameError>>()
|
||||||
|
.map_err(|err| PyPep508Error::new_err(err.to_string()))?;
|
||||||
|
|
||||||
|
let python_versions = python_versions
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|py_version| py_version.0)
|
.map(|py_version| py_version.0)
|
||||||
.collect::<Vec<_>>(),
|
.collect::<Vec<_>>();
|
||||||
)
|
|
||||||
|
Ok(self.evaluate_extras_and_python_version(&extras, &python_versions))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns whether the markers apply for the given environment
|
/// Returns whether the markers apply for the given environment
|
||||||
|
|
@ -302,8 +318,14 @@ impl Requirement {
|
||||||
&self,
|
&self,
|
||||||
env: &MarkerEnvironment,
|
env: &MarkerEnvironment,
|
||||||
extras: Vec<String>,
|
extras: Vec<String>,
|
||||||
) -> (bool, Vec<(MarkerWarningKind, String, String)>) {
|
) -> PyResult<(bool, Vec<MarkerWarning>)> {
|
||||||
self.evaluate_markers_and_report(env, &extras)
|
let extras = extras
|
||||||
|
.into_iter()
|
||||||
|
.map(|extra| ExtraName::from_str(&extra))
|
||||||
|
.collect::<Result<Vec<_>, InvalidNameError>>()
|
||||||
|
.map_err(|err| PyPep508Error::new_err(err.to_string()))?;
|
||||||
|
|
||||||
|
Ok(self.evaluate_markers_and_report(env, &extras))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -326,7 +348,7 @@ impl Requirement {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns whether the markers apply for the given environment
|
/// Returns whether the markers apply for the given environment
|
||||||
pub fn evaluate_markers(&self, env: &MarkerEnvironment, extras: &[&str]) -> bool {
|
pub fn evaluate_markers(&self, env: &MarkerEnvironment, extras: &[ExtraName]) -> bool {
|
||||||
if let Some(marker) = &self.marker {
|
if let Some(marker) = &self.marker {
|
||||||
marker.evaluate(env, extras)
|
marker.evaluate(env, extras)
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -342,7 +364,7 @@ impl Requirement {
|
||||||
/// with an environment and forward all warnings.
|
/// with an environment and forward all warnings.
|
||||||
pub fn evaluate_extras_and_python_version(
|
pub fn evaluate_extras_and_python_version(
|
||||||
&self,
|
&self,
|
||||||
extras: &HashSet<String>,
|
extras: &HashSet<ExtraName>,
|
||||||
python_versions: &[Version],
|
python_versions: &[Version],
|
||||||
) -> bool {
|
) -> bool {
|
||||||
if let Some(marker) = &self.marker {
|
if let Some(marker) = &self.marker {
|
||||||
|
|
@ -356,16 +378,10 @@ impl Requirement {
|
||||||
pub fn evaluate_markers_and_report(
|
pub fn evaluate_markers_and_report(
|
||||||
&self,
|
&self,
|
||||||
env: &MarkerEnvironment,
|
env: &MarkerEnvironment,
|
||||||
extras: &[String],
|
extras: &[ExtraName],
|
||||||
) -> (bool, Vec<(MarkerWarningKind, String, String)>) {
|
) -> (bool, Vec<MarkerWarning>) {
|
||||||
if let Some(marker) = &self.marker {
|
if let Some(marker) = &self.marker {
|
||||||
marker.evaluate_collect_warnings(
|
marker.evaluate_collect_warnings(env, extras)
|
||||||
env,
|
|
||||||
&extras
|
|
||||||
.iter()
|
|
||||||
.map(std::string::String::as_str)
|
|
||||||
.collect::<Vec<&str>>(),
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
(true, Vec::new())
|
(true, Vec::new())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@
|
||||||
|
|
||||||
use crate::{Cursor, Pep508Error, Pep508ErrorSource};
|
use crate::{Cursor, Pep508Error, Pep508ErrorSource};
|
||||||
use pep440_rs::{Version, VersionPattern, VersionSpecifier};
|
use pep440_rs::{Version, VersionPattern, VersionSpecifier};
|
||||||
|
use puffin_normalize::ExtraName;
|
||||||
#[cfg(feature = "pyo3")]
|
#[cfg(feature = "pyo3")]
|
||||||
use pyo3::{
|
use pyo3::{
|
||||||
basic::CompareOp, exceptions::PyValueError, pyclass, pymethods, PyAny, PyResult, Python,
|
basic::CompareOp, exceptions::PyValueError, pyclass, pymethods, PyAny, PyResult, Python,
|
||||||
|
|
@ -552,7 +553,7 @@ impl MarkerExpression {
|
||||||
fn evaluate(
|
fn evaluate(
|
||||||
&self,
|
&self,
|
||||||
env: &MarkerEnvironment,
|
env: &MarkerEnvironment,
|
||||||
extras: &[&str],
|
extras: &[ExtraName],
|
||||||
reporter: &mut impl FnMut(MarkerWarningKind, String, &MarkerExpression),
|
reporter: &mut impl FnMut(MarkerWarningKind, String, &MarkerExpression),
|
||||||
) -> bool {
|
) -> bool {
|
||||||
match &self.l_value {
|
match &self.l_value {
|
||||||
|
|
@ -630,7 +631,15 @@ impl MarkerExpression {
|
||||||
}
|
}
|
||||||
MarkerValue::QuotedString(r_value_string) => r_value_string,
|
MarkerValue::QuotedString(r_value_string) => r_value_string,
|
||||||
};
|
};
|
||||||
self.marker_compare(r_value_string, extras, reporter)
|
match ExtraName::from_str(r_value_string) {
|
||||||
|
Ok(r_extra) => extras.contains(&r_extra),
|
||||||
|
Err(err) => {
|
||||||
|
reporter(MarkerWarningKind::ExtraInvalidComparison, format!(
|
||||||
|
"Expected extra name, found '{r_value_string}', evaluating to false: {err}"
|
||||||
|
), self);
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// This is either MarkerEnvVersion, MarkerEnvString or Extra inverted
|
// This is either MarkerEnvVersion, MarkerEnvString or Extra inverted
|
||||||
MarkerValue::QuotedString(l_string) => {
|
MarkerValue::QuotedString(l_string) => {
|
||||||
|
|
@ -683,7 +692,15 @@ impl MarkerExpression {
|
||||||
self.compare_strings(l_string, r_string, reporter)
|
self.compare_strings(l_string, r_string, reporter)
|
||||||
}
|
}
|
||||||
// `'...' == extra`
|
// `'...' == extra`
|
||||||
MarkerValue::Extra => self.marker_compare(l_string, extras, reporter),
|
MarkerValue::Extra => match ExtraName::from_str(l_string) {
|
||||||
|
Ok(l_extra) => self.marker_compare(&l_extra, extras, reporter),
|
||||||
|
Err(err) => {
|
||||||
|
reporter(MarkerWarningKind::ExtraInvalidComparison, format!(
|
||||||
|
"Expected extra name, found '{l_string}', evaluating to false: {err}"
|
||||||
|
), self);
|
||||||
|
false
|
||||||
|
}
|
||||||
|
},
|
||||||
// `'...' == '...'`, doesn't make much sense
|
// `'...' == '...'`, doesn't make much sense
|
||||||
MarkerValue::QuotedString(_) => {
|
MarkerValue::QuotedString(_) => {
|
||||||
// Not even pypa/packaging 22.0 supports this
|
// Not even pypa/packaging 22.0 supports this
|
||||||
|
|
@ -730,25 +747,25 @@ impl MarkerExpression {
|
||||||
/// ```
|
/// ```
|
||||||
fn evaluate_extras_and_python_version(
|
fn evaluate_extras_and_python_version(
|
||||||
&self,
|
&self,
|
||||||
extras: &HashSet<String>,
|
extras: &HashSet<ExtraName>,
|
||||||
python_versions: &[Version],
|
python_versions: &[Version],
|
||||||
) -> bool {
|
) -> bool {
|
||||||
match (&self.l_value, &self.operator, &self.r_value) {
|
match (&self.l_value, &self.operator, &self.r_value) {
|
||||||
// `extra == '...'`
|
// `extra == '...'`
|
||||||
(MarkerValue::Extra, MarkerOperator::Equal, MarkerValue::QuotedString(r_string)) => {
|
(MarkerValue::Extra, MarkerOperator::Equal, MarkerValue::QuotedString(r_string)) => {
|
||||||
extras.contains(r_string)
|
ExtraName::from_str(r_string).is_ok_and(|r_extra| extras.contains(&r_extra))
|
||||||
}
|
}
|
||||||
// `'...' == extra`
|
// `'...' == extra`
|
||||||
(MarkerValue::QuotedString(l_string), MarkerOperator::Equal, MarkerValue::Extra) => {
|
(MarkerValue::QuotedString(l_string), MarkerOperator::Equal, MarkerValue::Extra) => {
|
||||||
extras.contains(l_string)
|
ExtraName::from_str(l_string).is_ok_and(|l_extra| extras.contains(&l_extra))
|
||||||
}
|
}
|
||||||
// `extra != '...'`
|
// `extra != '...'`
|
||||||
(MarkerValue::Extra, MarkerOperator::NotEqual, MarkerValue::QuotedString(r_string)) => {
|
(MarkerValue::Extra, MarkerOperator::NotEqual, MarkerValue::QuotedString(r_string)) => {
|
||||||
!extras.contains(r_string)
|
ExtraName::from_str(r_string).is_ok_and(|r_extra| !extras.contains(&r_extra))
|
||||||
}
|
}
|
||||||
// `'...' != extra`
|
// `'...' != extra`
|
||||||
(MarkerValue::QuotedString(l_string), MarkerOperator::NotEqual, MarkerValue::Extra) => {
|
(MarkerValue::QuotedString(l_string), MarkerOperator::NotEqual, MarkerValue::Extra) => {
|
||||||
!extras.contains(l_string)
|
ExtraName::from_str(l_string).is_ok_and(|l_extra| !extras.contains(&l_extra))
|
||||||
}
|
}
|
||||||
(
|
(
|
||||||
MarkerValue::MarkerEnvVersion(MarkerValueVersion::PythonVersion),
|
MarkerValue::MarkerEnvVersion(MarkerValueVersion::PythonVersion),
|
||||||
|
|
@ -860,14 +877,13 @@ impl MarkerExpression {
|
||||||
// The `marker <op> '...'` comparison
|
// The `marker <op> '...'` comparison
|
||||||
fn marker_compare(
|
fn marker_compare(
|
||||||
&self,
|
&self,
|
||||||
value: &str,
|
value: &ExtraName,
|
||||||
extras: &[&str],
|
extras: &[ExtraName],
|
||||||
reporter: &mut impl FnMut(MarkerWarningKind, String, &MarkerExpression),
|
reporter: &mut impl FnMut(MarkerWarningKind, String, &MarkerExpression),
|
||||||
) -> bool {
|
) -> bool {
|
||||||
match self.operator {
|
match self.operator {
|
||||||
// TODO: normalize extras
|
MarkerOperator::Equal => extras.contains(value),
|
||||||
MarkerOperator::Equal => extras.contains(&value),
|
MarkerOperator::NotEqual => !extras.contains(value),
|
||||||
MarkerOperator::NotEqual => !extras.contains(&value),
|
|
||||||
_ => {
|
_ => {
|
||||||
reporter(MarkerWarningKind::ExtraInvalidComparison, "Comparing extra with something other than equal (`==`) or unequal (`!=`) is wrong, evaluating to false".to_string(), self);
|
reporter(MarkerWarningKind::ExtraInvalidComparison, "Comparing extra with something other than equal (`==`) or unequal (`!=`) is wrong, evaluating to false".to_string(), self);
|
||||||
false
|
false
|
||||||
|
|
@ -926,7 +942,7 @@ impl FromStr for MarkerTree {
|
||||||
|
|
||||||
impl MarkerTree {
|
impl MarkerTree {
|
||||||
/// Does this marker apply in the given environment?
|
/// Does this marker apply in the given environment?
|
||||||
pub fn evaluate(&self, env: &MarkerEnvironment, extras: &[&str]) -> bool {
|
pub fn evaluate(&self, env: &MarkerEnvironment, extras: &[ExtraName]) -> bool {
|
||||||
let mut reporter = |_kind, message, _marker_expression: &MarkerExpression| {
|
let mut reporter = |_kind, message, _marker_expression: &MarkerExpression| {
|
||||||
warn!("{}", message);
|
warn!("{}", message);
|
||||||
};
|
};
|
||||||
|
|
@ -947,7 +963,7 @@ impl MarkerTree {
|
||||||
pub fn evaluate_reporter(
|
pub fn evaluate_reporter(
|
||||||
&self,
|
&self,
|
||||||
env: &MarkerEnvironment,
|
env: &MarkerEnvironment,
|
||||||
extras: &[&str],
|
extras: &[ExtraName],
|
||||||
reporter: &mut impl FnMut(MarkerWarningKind, String, &MarkerExpression),
|
reporter: &mut impl FnMut(MarkerWarningKind, String, &MarkerExpression),
|
||||||
) -> bool {
|
) -> bool {
|
||||||
self.report_deprecated_options(reporter);
|
self.report_deprecated_options(reporter);
|
||||||
|
|
@ -957,7 +973,7 @@ impl MarkerTree {
|
||||||
fn evaluate_reporter_impl(
|
fn evaluate_reporter_impl(
|
||||||
&self,
|
&self,
|
||||||
env: &MarkerEnvironment,
|
env: &MarkerEnvironment,
|
||||||
extras: &[&str],
|
extras: &[ExtraName],
|
||||||
reporter: &mut impl FnMut(MarkerWarningKind, String, &MarkerExpression),
|
reporter: &mut impl FnMut(MarkerWarningKind, String, &MarkerExpression),
|
||||||
) -> bool {
|
) -> bool {
|
||||||
match self {
|
match self {
|
||||||
|
|
@ -981,7 +997,7 @@ impl MarkerTree {
|
||||||
/// and forward all warnings.
|
/// and forward all warnings.
|
||||||
pub fn evaluate_extras_and_python_version(
|
pub fn evaluate_extras_and_python_version(
|
||||||
&self,
|
&self,
|
||||||
extras: &HashSet<String>,
|
extras: &HashSet<ExtraName>,
|
||||||
python_versions: &[Version],
|
python_versions: &[Version],
|
||||||
) -> bool {
|
) -> bool {
|
||||||
match self {
|
match self {
|
||||||
|
|
@ -1002,7 +1018,7 @@ impl MarkerTree {
|
||||||
pub fn evaluate_collect_warnings(
|
pub fn evaluate_collect_warnings(
|
||||||
&self,
|
&self,
|
||||||
env: &MarkerEnvironment,
|
env: &MarkerEnvironment,
|
||||||
extras: &[&str],
|
extras: &[ExtraName],
|
||||||
) -> (bool, Vec<(MarkerWarningKind, String, String)>) {
|
) -> (bool, Vec<(MarkerWarningKind, String, String)>) {
|
||||||
let mut warnings = Vec::new();
|
let mut warnings = Vec::new();
|
||||||
let mut reporter = |kind, warning, marker: &MarkerExpression| {
|
let mut reporter = |kind, warning, marker: &MarkerExpression| {
|
||||||
|
|
@ -1131,7 +1147,7 @@ fn parse_marker_operator(cursor: &mut Cursor) -> Result<MarkerOperator, Pep508Er
|
||||||
start: cursor.pos(),
|
start: cursor.pos(),
|
||||||
len: 1,
|
len: 1,
|
||||||
input: cursor.to_string(),
|
input: cursor.to_string(),
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
Some((_, whitespace)) if whitespace.is_whitespace() => {}
|
Some((_, whitespace)) if whitespace.is_whitespace() => {}
|
||||||
Some((pos, other)) => {
|
Some((pos, other)) => {
|
||||||
|
|
@ -1142,7 +1158,7 @@ fn parse_marker_operator(cursor: &mut Cursor) -> Result<MarkerOperator, Pep508Er
|
||||||
start: pos,
|
start: pos,
|
||||||
len: other.len_utf8(),
|
len: other.len_utf8(),
|
||||||
input: cursor.to_string(),
|
input: cursor.to_string(),
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
cursor.eat_whitespace();
|
cursor.eat_whitespace();
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ impl PubGrubDependencies {
|
||||||
|
|
||||||
// If the requirement isn't relevant for the current platform, skip it.
|
// If the requirement isn't relevant for the current platform, skip it.
|
||||||
if let Some(extra) = extra {
|
if let Some(extra) = extra {
|
||||||
if !requirement.evaluate_markers(env, &[extra.as_ref()]) {
|
if !requirement.evaluate_markers(env, std::slice::from_ref(extra)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -89,7 +89,7 @@ impl PubGrubDependencies {
|
||||||
|
|
||||||
// If the requirement isn't relevant for the current platform, skip it.
|
// If the requirement isn't relevant for the current platform, skip it.
|
||||||
if let Some(extra) = extra {
|
if let Some(extra) = extra {
|
||||||
if !constraint.evaluate_markers(env, &[extra.as_ref()]) {
|
if !constraint.evaluate_markers(env, std::slice::from_ref(extra)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue