Add variant support to uv-pep508

This commit is contained in:
konstin 2025-07-21 18:50:24 +02:00 committed by Charlie Marsh
parent de9f299b80
commit 4a90bef83e
5 changed files with 360 additions and 69 deletions

View File

@ -82,6 +82,17 @@ pub enum Pep508ErrorSource<T: Pep508Url = VerbatimUrl> {
/// The version requirement is not supported.
#[error("{0}")]
UnsupportedRequirement(String),
/// The operator is not supported with the variant marker.
#[error(
"The operator {0} is not supported with the marker {1}, only the `in` and `not in` operators are supported"
)]
ListOperator(MarkerOperator, MarkerValueList),
/// The value is not a quoted string.
#[error("Only quoted strings are supported with the variant marker {1}, not {0}")]
ListValue(MarkerValue, MarkerValueList),
/// The variant marker is on the left hand side of the expression.
#[error("The marker {0} must be on the right hand side of the expression")]
ListLValue(MarkerValueList),
}
impl<T: Pep508Url> Display for Pep508Error<T> {
@ -298,8 +309,13 @@ impl<T: Pep508Url> CacheKey for Requirement<T> {
impl<T: Pep508Url> Requirement<T> {
/// Returns whether the markers apply for the given environment
pub fn evaluate_markers(&self, env: &MarkerEnvironment, extras: &[ExtraName]) -> bool {
self.marker.evaluate(env, extras)
pub fn evaluate_markers(
&self,
env: &MarkerEnvironment,
variants: Option<&[(String, String, String)]>,
extras: &[ExtraName],
) -> bool {
self.marker.evaluate(env, variants, extras)
}
/// Return the requirement with an additional marker added, to require the given extra.

View File

@ -1,5 +1,4 @@
use std::fmt::{Display, Formatter};
use uv_normalize::{ExtraName, GroupName};
use crate::marker::tree::MarkerValueList;
@ -163,13 +162,19 @@ impl Display for CanonicalMarkerValueExtra {
/// A key-value pair for `<value> in <key>` or `<value> not in <key>`, where the key is a list.
///
/// Used for PEP 751 markers.
/// Used for PEP 751 and variant markers.
#[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
pub enum CanonicalMarkerListPair {
/// A valid [`ExtraName`].
Extras(ExtraName),
/// A valid [`GroupName`].
DependencyGroup(GroupName),
/// A valid `variant_namespaces`.
VariantNamespaces(String),
/// A valid `variant_features`.
VariantFeatures(String, String),
/// A valid `variant_properties`.
VariantProperties(String, String, String),
/// For leniency, preserve invalid values.
Arbitrary { key: MarkerValueList, value: String },
}
@ -180,6 +185,9 @@ impl CanonicalMarkerListPair {
match self {
Self::Extras(_) => MarkerValueList::Extras,
Self::DependencyGroup(_) => MarkerValueList::DependencyGroups,
Self::VariantNamespaces(_) => MarkerValueList::VariantNamespaces,
Self::VariantFeatures(_, _) => MarkerValueList::VariantFeatures,
Self::VariantProperties(_, _, _) => MarkerValueList::VariantProperties,
Self::Arbitrary { key, .. } => *key,
}
}
@ -189,6 +197,13 @@ impl CanonicalMarkerListPair {
match self {
Self::Extras(extra) => extra.to_string(),
Self::DependencyGroup(group) => group.to_string(),
Self::VariantNamespaces(namespace) => namespace.clone(),
Self::VariantFeatures(namespace, property) => {
format!("{namespace} :: {property}")
}
Self::VariantProperties(namespace, property, value) => {
format!("{namespace} :: {property} :: {value}")
}
Self::Arbitrary { value, .. } => value.clone(),
}
}

View File

@ -181,6 +181,9 @@ pub(crate) fn parse_marker_key_op_value<T: Pep508Url>(
let r_value = parse_marker_value(cursor, reporter)?;
let len = cursor.pos() - start;
// TODO(konsti): Catch incorrect variant markers in all places, now that we have the
// opportunity to check from the beginning.
// Convert a `<marker_value> <marker_op> <marker_value>` expression into its
// typed equivalent.
let expr = match l_value {
@ -307,7 +310,7 @@ pub(crate) fn parse_marker_key_op_value<T: Pep508Url>(
Ok(name) => CanonicalMarkerListPair::Extras(name),
Err(err) => {
reporter.report(
MarkerWarningKind::ExtrasInvalidComparison,
MarkerWarningKind::ListInvalidComparison,
format!("Expected extra name (found `{l_string}`): {err}"),
);
CanonicalMarkerListPair::Arbitrary {
@ -322,7 +325,7 @@ pub(crate) fn parse_marker_key_op_value<T: Pep508Url>(
Ok(name) => CanonicalMarkerListPair::DependencyGroup(name),
Err(err) => {
reporter.report(
MarkerWarningKind::ExtrasInvalidComparison,
MarkerWarningKind::ListInvalidComparison,
format!("Expected dependency group name (found `{l_string}`): {err}"),
);
CanonicalMarkerListPair::Arbitrary {
@ -332,6 +335,53 @@ pub(crate) fn parse_marker_key_op_value<T: Pep508Url>(
}
}
}
MarkerValueList::VariantNamespaces => {
// TODO(konsti): Validate
CanonicalMarkerListPair::VariantNamespaces(l_string.trim().to_string())
}
MarkerValueList::VariantFeatures => {
if let Some((namespace, feature)) = l_string.split_once("::") {
// TODO(konsti): Validate
CanonicalMarkerListPair::VariantFeatures(
namespace.trim().to_string(),
feature.trim().to_string(),
)
} else {
reporter.report(
MarkerWarningKind::ListInvalidComparison,
format!("Expected variant feature with two components seperated by `::`, found `{l_string}`"),
);
CanonicalMarkerListPair::Arbitrary {
key,
value: l_string.to_string(),
}
}
}
MarkerValueList::VariantProperties => {
let mut components = l_string.split("::");
if let (Some(namespace), Some(feature), Some(property), None) = (
components.next(),
components.next(),
components.next(),
components.next(),
) {
// TODO(konsti): Validate
CanonicalMarkerListPair::VariantProperties(
namespace.trim().to_string(),
feature.trim().to_string(),
property.trim().to_string(),
)
} else {
reporter.report(
MarkerWarningKind::ListInvalidComparison,
format!("Expected variant property with three components seperated by `::`, found `{l_string}`"),
);
CanonicalMarkerListPair::Arbitrary {
key,
value: l_string.to_string(),
}
}
}
};
Some(MarkerExpression::List { pair, operator })

View File

@ -33,12 +33,9 @@ pub enum MarkerWarningKind {
/// Doing an operation other than `==` and `!=` on a quoted string with `extra`, such as
/// `extra > "perf"` or `extra == os_name`
ExtraInvalidComparison,
/// Doing an operation other than `in` and `not in` on a quoted string with `extra`, such as
/// `extras > "perf"` or `extras == os_name`
ExtrasInvalidComparison,
/// Doing an operation other than `in` and `not in` on a quoted string with `dependency_groups`,
/// such as `dependency_groups > "perf"` or `dependency_groups == os_name`
DependencyGroupsInvalidComparison,
/// Doing an operation other than `in` and `not in` on a list marker, such as
/// `extras > "perf"` or `dependency_groups == os_name`
ListInvalidComparison,
/// Comparing a string valued marker and a string lexicographically, such as `"3.9" > "3.10"`
LexicographicComparison,
/// Comparing two markers, such as `os_name != sys_implementation`
@ -128,9 +125,15 @@ impl Display for MarkerValueString {
/// Those markers with exclusively `in` and `not in` operators.
///
/// Contains PEP 751 lockfile markers.
/// Contains the PEP 751 lockfile marker and the variant markers.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
pub enum MarkerValueList {
/// `variant_namespaces`
VariantNamespaces,
/// `variant_features`
VariantFeatures,
/// `variant_properties`
VariantProperties,
/// `extras`. This one is special because it's a list, and user-provided
Extras,
/// `dependency_groups`. This one is special because it's a list, and user-provided
@ -142,6 +145,9 @@ impl Display for MarkerValueList {
match self {
Self::Extras => f.write_str("extras"),
Self::DependencyGroups => f.write_str("dependency_groups"),
Self::VariantNamespaces => f.write_str("variant_namespaces"),
Self::VariantFeatures => f.write_str("variant_features"),
Self::VariantProperties => f.write_str("variant_properties"),
}
}
}
@ -200,6 +206,9 @@ impl FromStr for MarkerValue {
"sys.platform" => Self::MarkerEnvString(MarkerValueString::SysPlatformDeprecated),
"extras" => Self::MarkerEnvList(MarkerValueList::Extras),
"dependency_groups" => Self::MarkerEnvList(MarkerValueList::DependencyGroups),
"variant_namespaces" => Self::MarkerEnvList(MarkerValueList::VariantNamespaces),
"variant_features" => Self::MarkerEnvList(MarkerValueList::VariantFeatures),
"variant_properties" => Self::MarkerEnvList(MarkerValueList::VariantProperties),
"extra" => Self::Extra,
_ => return Err(format!("Invalid key: {s}")),
};
@ -531,7 +540,7 @@ pub enum MarkerExpression {
operator: MarkerOperator,
value: ArcStr,
},
/// `'...' in <key>`, a PEP 751 expression.
/// `'...' in <key>`, either PEP 751 or a variant expression.
List {
pair: CanonicalMarkerListPair,
operator: ContainerOperator,
@ -552,7 +561,8 @@ pub(crate) enum MarkerExpressionKind {
VersionIn(MarkerValueVersion),
/// A string marker comparison, e.g. `sys_platform == '...'`.
String(MarkerValueString),
/// A list `in` or `not in` expression, e.g. `'...' in dependency_groups`.
/// A list `in` or `not in` expression, e.g. `'...' in dependency_groups` or
/// `'gpu :: cuda :: cu128' in variant_properties`.
List(MarkerValueList),
/// An extra expression, e.g. `extra == '...'`.
Extra,
@ -996,10 +1006,16 @@ impl MarkerTree {
}
/// Does this marker apply in the given environment?
pub fn evaluate(self, env: &MarkerEnvironment, extras: &[ExtraName]) -> bool {
pub fn evaluate(
self,
env: &MarkerEnvironment,
variants: Option<&[(String, String, String)]>,
extras: &[ExtraName],
) -> bool {
self.evaluate_reporter_impl(
env,
ExtrasEnvironment::from_extras(extras),
variants,
&mut TracingReporter,
)
}
@ -1010,12 +1026,14 @@ impl MarkerTree {
pub fn evaluate_pep751(
self,
env: &MarkerEnvironment,
variants: Option<&[(String, String, String)]>,
extras: &[ExtraName],
groups: &[GroupName],
) -> bool {
self.evaluate_reporter_impl(
env,
ExtrasEnvironment::from_pep751(extras, groups),
variants,
&mut TracingReporter,
)
}
@ -1030,6 +1048,7 @@ impl MarkerTree {
pub fn evaluate_optional_environment(
self,
env: Option<&MarkerEnvironment>,
variants: Option<&[(String, String, String)]>,
extras: &[ExtraName],
) -> bool {
match env {
@ -1037,6 +1056,7 @@ impl MarkerTree {
Some(env) => self.evaluate_reporter_impl(
env,
ExtrasEnvironment::from_extras(extras),
variants,
&mut TracingReporter,
),
}
@ -1048,15 +1068,22 @@ impl MarkerTree {
self,
env: &MarkerEnvironment,
extras: &[ExtraName],
variants: Option<&[(String, String, String)]>,
reporter: &mut impl Reporter,
) -> bool {
self.evaluate_reporter_impl(env, ExtrasEnvironment::from_extras(extras), reporter)
self.evaluate_reporter_impl(
env,
ExtrasEnvironment::from_extras(extras),
variants,
reporter,
)
}
fn evaluate_reporter_impl(
self,
env: &MarkerEnvironment,
extras: ExtrasEnvironment,
variants: Option<&[(String, String, String)]>,
reporter: &mut impl Reporter,
) -> bool {
match self.kind() {
@ -1065,7 +1092,7 @@ impl MarkerTree {
MarkerTreeKind::Version(marker) => {
for (range, tree) in marker.edges() {
if range.contains(env.get_version(marker.key())) {
return tree.evaluate_reporter_impl(env, extras, reporter);
return tree.evaluate_reporter_impl(env, extras, variants, reporter);
}
}
}
@ -1092,27 +1119,56 @@ impl MarkerTree {
}
if range.contains(l_string) {
return tree.evaluate_reporter_impl(env, extras, reporter);
return tree.evaluate_reporter_impl(env, extras, variants, reporter);
}
}
}
MarkerTreeKind::In(marker) => {
return marker
.edge(marker.value().contains(env.get_string(marker.key())))
.evaluate_reporter_impl(env, extras, reporter);
.evaluate_reporter_impl(env, extras, variants, reporter);
}
MarkerTreeKind::Contains(marker) => {
return marker
.edge(env.get_string(marker.key()).contains(marker.value()))
.evaluate_reporter_impl(env, extras, reporter);
}
MarkerTreeKind::Extra(marker) => {
return marker
.edge(extras.extra().contains(marker.name().extra()))
.evaluate_reporter_impl(env, extras, reporter);
.evaluate_reporter_impl(env, extras, variants, reporter);
}
MarkerTreeKind::List(marker) => {
let edge = match marker.pair() {
CanonicalMarkerListPair::VariantNamespaces(marker_namespace) => {
let Some(variants) = variants else {
// If we're not limiting to specific variants, we're solving universally.
return true;
};
variants
.iter()
.any(|(namespace, _feature, _property)| namespace == marker_namespace)
}
CanonicalMarkerListPair::VariantFeatures(marker_namespace, marker_feature) => {
let Some(variants) = variants else {
return true;
};
variants.iter().any(|(namespace, feature, _property)| {
namespace == marker_namespace && feature == marker_feature
})
}
CanonicalMarkerListPair::VariantProperties(
marker_namespace,
marker_feature,
marker_property,
) => {
let Some(variants) = variants else {
return true;
};
variants.iter().any(|(namespace, feature, property)| {
namespace == marker_namespace
&& feature == marker_feature
&& property == marker_property
})
}
CanonicalMarkerListPair::Extras(extra) => extras.extras().contains(extra),
CanonicalMarkerListPair::DependencyGroup(dependency_group) => {
extras.dependency_groups().contains(dependency_group)
@ -1123,7 +1179,12 @@ impl MarkerTree {
return marker
.edge(edge)
.evaluate_reporter_impl(env, extras, reporter);
.evaluate_reporter_impl(env, extras, variants, reporter);
}
MarkerTreeKind::Extra(marker) => {
return marker
.edge(extras.extra().contains(marker.name().extra()))
.evaluate_reporter_impl(env, extras, variants, reporter);
}
}
@ -2150,12 +2211,12 @@ mod test {
let marker3 = MarkerTree::from_str(
"python_version == \"2.7\" and (sys_platform == \"win32\" or sys_platform == \"linux\")",
).unwrap();
assert!(marker1.evaluate(&env27, &[]));
assert!(!marker1.evaluate(&env37, &[]));
assert!(marker2.evaluate(&env27, &[]));
assert!(marker2.evaluate(&env37, &[]));
assert!(marker3.evaluate(&env27, &[]));
assert!(!marker3.evaluate(&env37, &[]));
assert!(marker1.evaluate(&env27, None, &[]));
assert!(!marker1.evaluate(&env37, None, &[]));
assert!(marker2.evaluate(&env27, None, &[]));
assert!(marker2.evaluate(&env37, None, &[]));
assert!(marker3.evaluate(&env27, None, &[]));
assert!(!marker3.evaluate(&env37, None, &[]));
}
#[test]
@ -2177,48 +2238,48 @@ mod test {
let env37 = env37();
let marker = MarkerTree::from_str("python_version in \"2.7 3.2 3.3\"").unwrap();
assert!(marker.evaluate(&env27, &[]));
assert!(!marker.evaluate(&env37, &[]));
assert!(marker.evaluate(&env27, None, &[]));
assert!(!marker.evaluate(&env37, None, &[]));
let marker = MarkerTree::from_str("python_version in \"2.7 3.7\"").unwrap();
assert!(marker.evaluate(&env27, &[]));
assert!(marker.evaluate(&env37, &[]));
assert!(marker.evaluate(&env27, None, &[]));
assert!(marker.evaluate(&env37, None, &[]));
let marker = MarkerTree::from_str("python_version in \"2.4 3.8 4.0\"").unwrap();
assert!(!marker.evaluate(&env27, &[]));
assert!(!marker.evaluate(&env37, &[]));
assert!(!marker.evaluate(&env27, None, &[]));
assert!(!marker.evaluate(&env37, None, &[]));
let marker = MarkerTree::from_str("python_version not in \"2.7 3.2 3.3\"").unwrap();
assert!(!marker.evaluate(&env27, &[]));
assert!(marker.evaluate(&env37, &[]));
assert!(!marker.evaluate(&env27, None, &[]));
assert!(marker.evaluate(&env37, None, &[]));
let marker = MarkerTree::from_str("python_version not in \"2.7 3.7\"").unwrap();
assert!(!marker.evaluate(&env27, &[]));
assert!(!marker.evaluate(&env37, &[]));
assert!(!marker.evaluate(&env27, None, &[]));
assert!(!marker.evaluate(&env37, None, &[]));
let marker = MarkerTree::from_str("python_version not in \"2.4 3.8 4.0\"").unwrap();
assert!(marker.evaluate(&env27, &[]));
assert!(marker.evaluate(&env37, &[]));
assert!(marker.evaluate(&env27, None, &[]));
assert!(marker.evaluate(&env37, None, &[]));
let marker = MarkerTree::from_str("python_full_version in \"2.7\"").unwrap();
assert!(marker.evaluate(&env27, &[]));
assert!(!marker.evaluate(&env37, &[]));
assert!(marker.evaluate(&env27, None, &[]));
assert!(!marker.evaluate(&env37, None, &[]));
let marker = MarkerTree::from_str("implementation_version in \"2.7 3.2 3.3\"").unwrap();
assert!(marker.evaluate(&env27, &[]));
assert!(!marker.evaluate(&env37, &[]));
assert!(marker.evaluate(&env27, None, &[]));
assert!(!marker.evaluate(&env37, None, &[]));
let marker = MarkerTree::from_str("implementation_version in \"2.7 3.7\"").unwrap();
assert!(marker.evaluate(&env27, &[]));
assert!(marker.evaluate(&env37, &[]));
assert!(marker.evaluate(&env27, None, &[]));
assert!(marker.evaluate(&env37, None, &[]));
let marker = MarkerTree::from_str("implementation_version not in \"2.7 3.7\"").unwrap();
assert!(!marker.evaluate(&env27, &[]));
assert!(!marker.evaluate(&env37, &[]));
assert!(!marker.evaluate(&env27, None, &[]));
assert!(!marker.evaluate(&env37, None, &[]));
let marker = MarkerTree::from_str("implementation_version not in \"2.4 3.8 4.0\"").unwrap();
assert!(marker.evaluate(&env27, &[]));
assert!(marker.evaluate(&env37, &[]));
assert!(marker.evaluate(&env27, None, &[]));
assert!(marker.evaluate(&env37, None, &[]));
}
#[test]
@ -2227,7 +2288,7 @@ mod test {
fn warnings1() {
let env37 = env37();
let compare_keys = MarkerTree::from_str("platform_version == sys_platform").unwrap();
compare_keys.evaluate(&env37, &[]);
compare_keys.evaluate(&env37, None, &[]);
logs_contain(
"Comparing two markers with each other doesn't make any sense, will evaluate to false",
);
@ -2239,7 +2300,7 @@ mod test {
fn warnings2() {
let env37 = env37();
let non_pep440 = MarkerTree::from_str("python_version >= '3.9.'").unwrap();
non_pep440.evaluate(&env37, &[]);
non_pep440.evaluate(&env37, None, &[]);
logs_contain(
"Expected PEP 440 version to compare with python_version, found `3.9.`, \
will evaluate to false: after parsing `3.9`, found `.`, which is \
@ -2253,7 +2314,7 @@ mod test {
fn warnings3() {
let env37 = env37();
let string_string = MarkerTree::from_str("'b' >= 'a'").unwrap();
string_string.evaluate(&env37, &[]);
string_string.evaluate(&env37, None, &[]);
logs_contain(
"Comparing two quoted strings with each other doesn't make sense: 'b' >= 'a', will evaluate to false",
);
@ -2265,7 +2326,7 @@ mod test {
fn warnings4() {
let env37 = env37();
let string_string = MarkerTree::from_str(r"os.name == 'posix' and platform.machine == 'x86_64' and platform.python_implementation == 'CPython' and 'Ubuntu' in platform.version and sys.platform == 'linux'").unwrap();
string_string.evaluate(&env37, &[]);
string_string.evaluate(&env37, None, &[]);
logs_assert(|lines: &[&str]| {
let lines: Vec<_> = lines
.iter()
@ -2297,18 +2358,18 @@ mod test {
let env37 = env37();
let result = MarkerTree::from_str("python_version > '3.6'")
.unwrap()
.evaluate(&env37, &[]);
.evaluate(&env37, None, &[]);
assert!(result);
let result = MarkerTree::from_str("'3.6' > python_version")
.unwrap()
.evaluate(&env37, &[]);
.evaluate(&env37, None, &[]);
assert!(!result);
// Meaningless expressions are ignored, so this is always true.
let result = MarkerTree::from_str("'3.*' == python_version")
.unwrap()
.evaluate(&env37, &[]);
.evaluate(&env37, None, &[]);
assert!(result);
}
@ -2317,12 +2378,12 @@ mod test {
let env37 = env37();
let result = MarkerTree::from_str("'nux' in sys_platform")
.unwrap()
.evaluate(&env37, &[]);
.evaluate(&env37, None, &[]);
assert!(result);
let result = MarkerTree::from_str("sys_platform in 'nux'")
.unwrap()
.evaluate(&env37, &[]);
.evaluate(&env37, None, &[]);
assert!(!result);
}
@ -2331,7 +2392,7 @@ mod test {
let env37 = env37();
let result = MarkerTree::from_str("python_version == '3.7.*'")
.unwrap()
.evaluate(&env37, &[]);
.evaluate(&env37, None, &[]);
assert!(result);
}
@ -2340,7 +2401,7 @@ mod test {
let env37 = env37();
let result = MarkerTree::from_str("python_version ~= '3.7'")
.unwrap()
.evaluate(&env37, &[]);
.evaluate(&env37, None, &[]);
assert!(result);
}
@ -3709,4 +3770,146 @@ mod test {
assert!(!marker.evaluate_only_extras(std::slice::from_ref(&b)));
assert!(marker.evaluate_only_extras(&[a.clone(), b.clone()]));
}
#[test]
fn marker_evaluation_variants() {
let env37 = env37();
let gpu_namespaces = [("gpu".to_string(), "cuda".to_string(), "12.4".to_string())];
let cpu_namespaces = [("cpu".to_string(), String::new(), String::new())];
// namespace variant markers
let marker1 = m("'gpu' in variant_namespaces");
let marker2 = m("'gpu' not in variant_namespaces");
// If no variants are provided, we solve universally.
assert!(marker1.evaluate(&env37, None, &[]));
assert!(marker2.evaluate(&env37, None, &[]));
assert!(marker1.evaluate(&env37, Some(&gpu_namespaces), &[]));
assert!(!marker1.evaluate(&env37, Some(&cpu_namespaces), &[]));
assert!(!marker2.evaluate(&env37, Some(&gpu_namespaces), &[]));
assert!(marker2.evaluate(&env37, Some(&cpu_namespaces), &[]));
// property variant markers
let marker3 = m("'gpu :: cuda' in variant_features");
let marker4 = m("'gpu :: rocm' in variant_features");
assert!(marker3.evaluate(&env37, None, &[]));
assert!(marker4.evaluate(&env37, None, &[]));
assert!(marker3.evaluate(&env37, Some(&gpu_namespaces), &[]));
assert!(!marker3.evaluate(&env37, Some(&cpu_namespaces), &[]));
assert!(!marker4.evaluate(&env37, Some(&gpu_namespaces), &[]));
assert!(!marker4.evaluate(&env37, Some(&cpu_namespaces), &[]));
// feature variant markers
let marker5 = m("'gpu :: cuda :: 12.4' in variant_properties");
let marker6 = m("'gpu :: cuda :: 12.8' in variant_properties");
assert!(marker5.evaluate(&env37, None, &[]));
assert!(marker6.evaluate(&env37, None, &[]));
assert!(marker5.evaluate(&env37, Some(&gpu_namespaces), &[]));
assert!(!marker5.evaluate(&env37, Some(&cpu_namespaces), &[]));
assert!(!marker6.evaluate(&env37, Some(&gpu_namespaces), &[]));
assert!(!marker6.evaluate(&env37, Some(&cpu_namespaces), &[]));
}
#[test]
fn marker_evaluation_variants_combined() {
let env37 = env37();
let namespaces = [
("gpu".to_string(), "cuda".to_string(), "12.4".to_string()),
("gpu".to_string(), "cuda".to_string(), "12.6".to_string()),
("cpu".to_string(), "x86_64".to_string(), "v1".to_string()),
("cpu".to_string(), "x86_64".to_string(), "v2".to_string()),
("cpu".to_string(), "x86_64".to_string(), "v3".to_string()),
];
let marker1 = m("'gpu' in variant_namespaces \
and 'cpu:: x86_64 :: v3' in variant_properties \
and python_version >= '3.7' \
and 'gpu :: rocm' not in variant_features");
assert!(marker1.evaluate(&env37, None, &[]));
assert!(marker1.evaluate(&env37, Some(&namespaces), &[]));
let marker2 = m("python_version >= '3.7' and 'gpu' not in variant_namespaces");
assert!(marker2.evaluate(&env37, None, &[]));
assert!(!marker2.evaluate(&env37, Some(&namespaces), &[]));
}
#[test]
fn variant_to_string() {
let assert_roundtrips = |marker| {
assert_eq!(m(marker).try_to_string().unwrap(), marker);
};
assert_roundtrips("'gpu' in variant_namespaces");
assert_roundtrips("'gpu' not in variant_namespaces");
assert_roundtrips("'gpu :: cuda' in variant_properties");
assert_roundtrips("'gpu :: cuda' not in variant_properties");
assert_roundtrips("'gpu :: cuda :: 12.4' in variant_features");
assert_roundtrips("'gpu :: cuda :: 12.8' not in variant_features");
// TODO(konsti): Implement normalization and test it.
}
#[test]
fn variant_errors() {
let err = MarkerExpression::from_str(r"variant_namespaces in 'gpu'")
.unwrap_err()
.to_string();
assert_snapshot!(
err,
@r"
The marker variant_namespaces must be on the right hand side of the expression
variant_namespaces in 'gpu'
^^^^^^^^^^^^^^^^^^^^^^^^^^^
"
);
let err = MarkerExpression::from_str(r"'gpu :: cuda' == variant_properties")
.unwrap_err()
.to_string();
assert_snapshot!(
err,
@r"
The operator == is not supported with the marker variant_properties, only the `in` and `not in` operators are supported
'gpu :: cuda' == variant_properties
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
"
);
// TODO(konsti): Test all cases systematically
}
#[test]
fn variant_invalid() {
let env37 = env37();
let namespaces = [("gpu".to_string(), "cuda".to_string(), "cuda126".to_string())];
let marker = m(r"'gpu :: cuda :: cuda126 :: gtx1080' in variant_properties");
assert!(!marker.evaluate(&env37, Some(&namespaces), &[]));
}
#[test]
fn torch_variant_marker() {
let env37 = env37();
let cu126 = [("nvidia".to_string(), "ctk".to_string(), "12.6".to_string())];
let cu126_2 = [
("nvidia".to_string(), "ctk".to_string(), "12.6".to_string()),
(
"nvidia".to_string(),
"cuda_version".to_string(),
">=12.6,<13".to_string(),
),
];
let cu128 = [("nvidia".to_string(), "ctk".to_string(), "12.8".to_string())];
let marker = m(
" platform_machine == 'x86_64' and sys_platform == 'linux' and 'nvidia :: ctk :: 12.6' in variant_properties",
);
assert!(marker.evaluate(&env37, None, &[]));
assert!(marker.evaluate(&env37, Some(&cu126), &[]));
assert!(marker.evaluate(&env37, Some(&cu126_2), &[]));
assert!(!marker.evaluate(&env37, Some(&cu128), &[]));
}
}

View File

@ -82,17 +82,24 @@ pub struct UnnamedRequirement<ReqUrl: UnnamedRequirementUrl = VerbatimUrl> {
impl<Url: UnnamedRequirementUrl> UnnamedRequirement<Url> {
/// Returns whether the markers apply for the given environment
pub fn evaluate_markers(&self, env: &MarkerEnvironment, extras: &[ExtraName]) -> bool {
self.evaluate_optional_environment(Some(env), extras)
pub fn evaluate_markers(
&self,
env: &MarkerEnvironment,
variants: Option<&[(String, String, String)]>,
extras: &[ExtraName],
) -> bool {
self.evaluate_optional_environment(Some(env), variants, extras)
}
/// Returns whether the markers apply for the given environment
pub fn evaluate_optional_environment(
&self,
env: Option<&MarkerEnvironment>,
variants: Option<&[(String, String, String)]>,
extras: &[ExtraName],
) -> bool {
self.marker.evaluate_optional_environment(env, extras)
self.marker
.evaluate_optional_environment(env, variants, extras)
}
/// Set the source file containing the requirement.