Use generic marker variant mechanism

This commit is contained in:
konstin 2025-07-25 19:22:40 +02:00
parent 1fcf0ebe52
commit c406b70965
26 changed files with 425 additions and 313 deletions

View File

@ -11,7 +11,8 @@ use uv_git_types::{GitOid, GitReference, GitUrl, GitUrlParseError, OidParseError
use uv_normalize::{ExtraName, GroupName, PackageName}; use uv_normalize::{ExtraName, GroupName, PackageName};
use uv_pep440::VersionSpecifiers; use uv_pep440::VersionSpecifiers;
use uv_pep508::{ use uv_pep508::{
MarkerEnvironment, MarkerTree, RequirementOrigin, VerbatimUrl, VersionOrUrl, marker, MarkerEnvironment, MarkerTree, MarkerVariantsEnvironment, RequirementOrigin, VerbatimUrl,
VersionOrUrl, marker,
}; };
use uv_redacted::DisplaySafeUrl; use uv_redacted::DisplaySafeUrl;
@ -72,7 +73,7 @@ impl Requirement {
pub fn evaluate_markers( pub fn evaluate_markers(
&self, &self,
env: Option<&MarkerEnvironment>, env: Option<&MarkerEnvironment>,
variants: Option<&[(String, String, String)]>, variants: impl MarkerVariantsEnvironment,
extras: &[ExtraName], extras: &[ExtraName],
) -> bool { ) -> bool {
self.marker self.marker

View File

@ -2,7 +2,7 @@ use std::borrow::Cow;
use std::fmt::{Display, Formatter}; use std::fmt::{Display, Formatter};
use uv_normalize::ExtraName; use uv_normalize::ExtraName;
use uv_pep508::{MarkerEnvironment, UnnamedRequirement}; use uv_pep508::{MarkerEnvironment, MarkerVariantsEnvironment, UnnamedRequirement};
use uv_pypi_types::Hashes; use uv_pypi_types::Hashes;
use crate::{Requirement, RequirementSource, VerbatimParsedUrl}; use crate::{Requirement, RequirementSource, VerbatimParsedUrl};
@ -62,7 +62,7 @@ impl UnresolvedRequirement {
pub fn evaluate_markers( pub fn evaluate_markers(
&self, &self,
env: Option<&MarkerEnvironment>, env: Option<&MarkerEnvironment>,
variants: Option<&[(String, String, String)]>, variants: impl MarkerVariantsEnvironment,
extras: &[ExtraName], extras: &[ExtraName],
) -> bool { ) -> bool {
match self { match self {

View File

@ -641,7 +641,7 @@ impl<'a, Context: BuildContext> DistributionDatabase<'a, Context> {
// TODO(konsti): We shouldn't need to do this conversion. // TODO(konsti): We shouldn't need to do this conversion.
let mut known_properties = BTreeSet::default(); let mut known_properties = BTreeSet::default();
for variant in variants_json.variants.values() { for variant in variants_json.variants.values() {
for (namespace, features) in variant { for (namespace, features) in &**variant {
for (feature, value) in features { for (feature, value) in features {
for value in value { for value in value {
known_properties.insert(VariantPropertyType { known_properties.insert(VariantPropertyType {

View File

@ -14,7 +14,7 @@ use uv_distribution_types::{
use uv_fs::Simplified; use uv_fs::Simplified;
use uv_normalize::PackageName; use uv_normalize::PackageName;
use uv_pep440::{Version, VersionSpecifiers}; use uv_pep440::{Version, VersionSpecifiers};
use uv_pep508::VersionOrUrl; use uv_pep508::{MarkerVariantsUniversal, VersionOrUrl};
use uv_pypi_types::{ResolverMarkerEnvironment, VerbatimParsedUrl}; use uv_pypi_types::{ResolverMarkerEnvironment, VerbatimParsedUrl};
use uv_python::{Interpreter, PythonEnvironment}; use uv_python::{Interpreter, PythonEnvironment};
use uv_redacted::DisplaySafeUrl; use uv_redacted::DisplaySafeUrl;
@ -243,7 +243,7 @@ impl SitePackages {
// Verify that the dependencies are installed. // Verify that the dependencies are installed.
for dependency in &metadata.requires_dist { for dependency in &metadata.requires_dist {
if !dependency.evaluate_markers(markers, None, &[]) { if !dependency.evaluate_markers(markers, MarkerVariantsUniversal, &[]) {
continue; continue;
} }
@ -416,7 +416,7 @@ impl SitePackages {
for requirement in requirements { for requirement in requirements {
if let Some(r#overrides) = overrides.get(&requirement.name) { if let Some(r#overrides) = overrides.get(&requirement.name) {
for dependency in r#overrides { for dependency in r#overrides {
if dependency.evaluate_markers(Some(markers), None, &[]) { if dependency.evaluate_markers(Some(markers), MarkerVariantsUniversal, &[]) {
if seen.insert((*dependency).clone()) { if seen.insert((*dependency).clone()) {
stack.push(Cow::Borrowed(*dependency)); stack.push(Cow::Borrowed(*dependency));
} }
@ -424,7 +424,7 @@ impl SitePackages {
} }
} else { } else {
// TODO(konsti): Evaluate variants // TODO(konsti): Evaluate variants
if requirement.evaluate_markers(Some(markers), None, &[]) { if requirement.evaluate_markers(Some(markers), MarkerVariantsUniversal, &[]) {
if seen.insert(requirement.clone()) { if seen.insert(requirement.clone()) {
stack.push(Cow::Borrowed(requirement)); stack.push(Cow::Borrowed(requirement));
} }
@ -443,7 +443,7 @@ impl SitePackages {
} }
[distribution] => { [distribution] => {
// Validate that the requirement is satisfied. // Validate that the requirement is satisfied.
if requirement.evaluate_markers(Some(markers), None, &[]) { if requirement.evaluate_markers(Some(markers), MarkerVariantsUniversal, &[]) {
match RequirementSatisfaction::check(distribution, &requirement.source) { match RequirementSatisfaction::check(distribution, &requirement.source) {
RequirementSatisfaction::Mismatch RequirementSatisfaction::Mismatch
| RequirementSatisfaction::OutOfDate | RequirementSatisfaction::OutOfDate
@ -456,7 +456,8 @@ impl SitePackages {
// Validate that the installed version satisfies the constraints. // Validate that the installed version satisfies the constraints.
for constraint in constraints.get(name).into_iter().flatten() { for constraint in constraints.get(name).into_iter().flatten() {
if constraint.evaluate_markers(Some(markers), None, &[]) { if constraint.evaluate_markers(Some(markers), MarkerVariantsUniversal, &[])
{
match RequirementSatisfaction::check(distribution, &constraint.source) { match RequirementSatisfaction::check(distribution, &constraint.source) {
RequirementSatisfaction::Mismatch RequirementSatisfaction::Mismatch
| RequirementSatisfaction::OutOfDate | RequirementSatisfaction::OutOfDate
@ -482,7 +483,7 @@ impl SitePackages {
for dependency in r#overrides { for dependency in r#overrides {
if dependency.evaluate_markers( if dependency.evaluate_markers(
Some(markers), Some(markers),
None, MarkerVariantsUniversal,
&requirement.extras, &requirement.extras,
) { ) {
if seen.insert((*dependency).clone()) { if seen.insert((*dependency).clone()) {
@ -491,8 +492,11 @@ impl SitePackages {
} }
} }
} else { } else {
if dependency.evaluate_markers(Some(markers), None, &requirement.extras) if dependency.evaluate_markers(
{ Some(markers),
MarkerVariantsUniversal,
&requirement.extras,
) {
if seen.insert(dependency.clone()) { if seen.insert(dependency.clone()) {
stack.push(Cow::Owned(dependency)); stack.push(Cow::Owned(dependency));
} }

View File

@ -33,7 +33,8 @@ pub use marker::{
ContainsMarkerTree, ExtraMarkerTree, ExtraOperator, InMarkerTree, MarkerEnvironment, ContainsMarkerTree, ExtraMarkerTree, ExtraOperator, InMarkerTree, MarkerEnvironment,
MarkerEnvironmentBuilder, MarkerExpression, MarkerOperator, MarkerTree, MarkerTreeContents, MarkerEnvironmentBuilder, MarkerExpression, MarkerOperator, MarkerTree, MarkerTreeContents,
MarkerTreeKind, MarkerValue, MarkerValueExtra, MarkerValueList, MarkerValueString, MarkerTreeKind, MarkerValue, MarkerValueExtra, MarkerValueList, MarkerValueString,
MarkerValueVersion, MarkerWarningKind, StringMarkerTree, StringVersion, VersionMarkerTree, MarkerValueVersion, MarkerVariantsEnvironment, MarkerVariantsUniversal, MarkerWarningKind,
StringMarkerTree, StringVersion, VersionMarkerTree,
}; };
pub use origin::RequirementOrigin; pub use origin::RequirementOrigin;
#[cfg(feature = "non-pep508-extensions")] #[cfg(feature = "non-pep508-extensions")]
@ -267,7 +268,7 @@ impl<T: Pep508Url> Requirement<T> {
pub fn evaluate_markers( pub fn evaluate_markers(
&self, &self,
env: &MarkerEnvironment, env: &MarkerEnvironment,
variants: Option<&[(String, String, String)]>, variants: impl MarkerVariantsEnvironment,
extras: &[ExtraName], extras: &[ExtraName],
) -> bool { ) -> bool {
self.marker.evaluate(env, variants, extras) self.marker.evaluate(env, variants, extras)

View File

@ -24,7 +24,8 @@ pub use tree::{
ContainsMarkerTree, ExtraMarkerTree, ExtraOperator, InMarkerTree, MarkerExpression, ContainsMarkerTree, ExtraMarkerTree, ExtraOperator, InMarkerTree, MarkerExpression,
MarkerOperator, MarkerTree, MarkerTreeContents, MarkerTreeDebugGraph, MarkerTreeKind, MarkerOperator, MarkerTree, MarkerTreeContents, MarkerTreeDebugGraph, MarkerTreeKind,
MarkerValue, MarkerValueExtra, MarkerValueList, MarkerValueString, MarkerValueVersion, MarkerValue, MarkerValueExtra, MarkerValueList, MarkerValueString, MarkerValueVersion,
MarkerWarningKind, StringMarkerTree, StringVersion, VersionMarkerTree, MarkerVariantsEnvironment, MarkerVariantsUniversal, MarkerWarningKind, StringMarkerTree,
StringVersion, VersionMarkerTree,
}; };
/// `serde` helpers for [`MarkerTree`]. /// `serde` helpers for [`MarkerTree`].

View File

@ -773,6 +773,73 @@ impl<'a> ExtrasEnvironment<'a> {
} }
} }
/// The environment to use for evaluating `variant_namespaces`, `variant_features` and
/// `variant_properties`
pub trait MarkerVariantsEnvironment {
/// Whether there is a property with the given namespace, for evaluating `variant_namespaces`.
fn has_namespace(&self, namespace: &str) -> bool;
/// Whether there is a property with the given feature, for evaluating `variant_features`.
fn has_feature(&self, namespace: &str, feature: &str) -> bool;
/// Whether there is the given property, for evaluating `variant_properties`.
fn has_property(&self, namespace: &str, feature: &str, property: &str) -> bool;
}
// Useful for tests
impl<S: AsRef<str>> MarkerVariantsEnvironment for &[(S, S, S)] {
fn has_namespace(&self, namespace: &str) -> bool {
self.iter()
.any(|(namespace_, _feature, _property)| namespace == namespace_.as_ref())
}
fn has_feature(&self, namespace: &str, feature: &str) -> bool {
self.iter().any(|(namespace_, feature_, _property)| {
namespace == namespace_.as_ref() && feature == feature_.as_ref()
})
}
fn has_property(&self, namespace: &str, feature: &str, property: &str) -> bool {
self.iter().any(|(namespace_, feature_, property_)| {
namespace == namespace_.as_ref()
&& feature == feature_.as_ref()
&& property == property_.as_ref()
})
}
}
// TODO(konsti): Use `AsRef` instead?
impl<T: MarkerVariantsEnvironment> MarkerVariantsEnvironment for &T {
fn has_namespace(&self, namespace: &str) -> bool {
T::has_namespace(self, namespace)
}
fn has_feature(&self, namespace: &str, feature: &str) -> bool {
T::has_feature(self, namespace, feature)
}
fn has_property(&self, namespace: &str, feature: &str, property: &str) -> bool {
T::has_property(self, namespace, feature, property)
}
}
/// A marker variants environment that always evaluates to `true`.
pub struct MarkerVariantsUniversal;
impl MarkerVariantsEnvironment for MarkerVariantsUniversal {
fn has_namespace(&self, _namespace: &str) -> bool {
true
}
fn has_feature(&self, _namespace: &str, _feature: &str) -> bool {
true
}
fn has_property(&self, _namespace: &str, _feature: &str, _property: &str) -> bool {
true
}
}
/// Represents one or more nested marker expressions with and/or/parentheses. /// Represents one or more nested marker expressions with and/or/parentheses.
/// ///
/// Marker trees are canonical, meaning any two functionally equivalent markers /// Marker trees are canonical, meaning any two functionally equivalent markers
@ -1012,7 +1079,7 @@ impl MarkerTree {
pub fn evaluate( pub fn evaluate(
self, self,
env: &MarkerEnvironment, env: &MarkerEnvironment,
variants: Option<&[(String, String, String)]>, variants: impl MarkerVariantsEnvironment,
extras: &[ExtraName], extras: &[ExtraName],
) -> bool { ) -> bool {
self.evaluate_reporter_impl( self.evaluate_reporter_impl(
@ -1029,7 +1096,7 @@ impl MarkerTree {
pub fn evaluate_pep751( pub fn evaluate_pep751(
self, self,
env: &MarkerEnvironment, env: &MarkerEnvironment,
variants: Option<&[(String, String, String)]>, variants: impl MarkerVariantsEnvironment,
extras: &[ExtraName], extras: &[ExtraName],
groups: &[GroupName], groups: &[GroupName],
) -> bool { ) -> bool {
@ -1051,7 +1118,7 @@ impl MarkerTree {
pub fn evaluate_optional_environment( pub fn evaluate_optional_environment(
self, self,
env: Option<&MarkerEnvironment>, env: Option<&MarkerEnvironment>,
variants: Option<&[(String, String, String)]>, variants: impl MarkerVariantsEnvironment,
extras: &[ExtraName], extras: &[ExtraName],
) -> bool { ) -> bool {
match env { match env {
@ -1071,7 +1138,7 @@ impl MarkerTree {
self, self,
env: &MarkerEnvironment, env: &MarkerEnvironment,
extras: &[ExtraName], extras: &[ExtraName],
variants: Option<&[(String, String, String)]>, variants: impl MarkerVariantsEnvironment,
reporter: &mut impl Reporter, reporter: &mut impl Reporter,
) -> bool { ) -> bool {
self.evaluate_reporter_impl( self.evaluate_reporter_impl(
@ -1086,7 +1153,7 @@ impl MarkerTree {
self, self,
env: &MarkerEnvironment, env: &MarkerEnvironment,
extras: ExtrasEnvironment, extras: ExtrasEnvironment,
variants: Option<&[(String, String, String)]>, variants: impl MarkerVariantsEnvironment,
reporter: &mut impl Reporter, reporter: &mut impl Reporter,
) -> bool { ) -> bool {
match self.kind() { match self.kind() {
@ -1138,39 +1205,14 @@ impl MarkerTree {
} }
MarkerTreeKind::List(marker) => { MarkerTreeKind::List(marker) => {
let edge = match marker.pair() { let edge = match marker.pair() {
CanonicalMarkerListPair::VariantNamespaces(marker_namespace) => { CanonicalMarkerListPair::VariantNamespaces(namespace) => {
let Some(variants) = variants else { variants.has_namespace(namespace)
// 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) => { CanonicalMarkerListPair::VariantFeatures(namespace, feature) => {
let Some(variants) = variants else { variants.has_feature(namespace, feature)
return true;
};
variants.iter().any(|(namespace, feature, _property)| {
namespace == marker_namespace && feature == marker_feature
})
} }
CanonicalMarkerListPair::VariantProperties( CanonicalMarkerListPair::VariantProperties(namespace, feature, property) => {
marker_namespace, variants.has_property(namespace, feature, property)
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::Extras(extra) => extras.extras().contains(extra),
CanonicalMarkerListPair::DependencyGroup(dependency_group) => { CanonicalMarkerListPair::DependencyGroup(dependency_group) => {
@ -2057,7 +2099,9 @@ mod test {
use uv_pep440::Version; use uv_pep440::Version;
use crate::marker::{MarkerEnvironment, MarkerEnvironmentBuilder}; use crate::marker::{MarkerEnvironment, MarkerEnvironmentBuilder};
use crate::{MarkerExpression, MarkerOperator, MarkerTree, MarkerValueString}; use crate::{
MarkerExpression, MarkerOperator, MarkerTree, MarkerValueString, MarkerVariantsUniversal,
};
fn parse_err(input: &str) -> String { fn parse_err(input: &str) -> String {
MarkerTree::from_str(input).unwrap_err().to_string() MarkerTree::from_str(input).unwrap_err().to_string()
@ -2214,12 +2258,12 @@ mod test {
let marker3 = MarkerTree::from_str( let marker3 = MarkerTree::from_str(
"python_version == \"2.7\" and (sys_platform == \"win32\" or sys_platform == \"linux\")", "python_version == \"2.7\" and (sys_platform == \"win32\" or sys_platform == \"linux\")",
).unwrap(); ).unwrap();
assert!(marker1.evaluate(&env27, None, &[])); assert!(marker1.evaluate(&env27, MarkerVariantsUniversal, &[]));
assert!(!marker1.evaluate(&env37, None, &[])); assert!(!marker1.evaluate(&env37, MarkerVariantsUniversal, &[]));
assert!(marker2.evaluate(&env27, None, &[])); assert!(marker2.evaluate(&env27, MarkerVariantsUniversal, &[]));
assert!(marker2.evaluate(&env37, None, &[])); assert!(marker2.evaluate(&env37, MarkerVariantsUniversal, &[]));
assert!(marker3.evaluate(&env27, None, &[])); assert!(marker3.evaluate(&env27, MarkerVariantsUniversal, &[]));
assert!(!marker3.evaluate(&env37, None, &[])); assert!(!marker3.evaluate(&env37, MarkerVariantsUniversal, &[]));
} }
#[test] #[test]
@ -2241,48 +2285,48 @@ mod test {
let env37 = env37(); let env37 = env37();
let marker = MarkerTree::from_str("python_version in \"2.7 3.2 3.3\"").unwrap(); let marker = MarkerTree::from_str("python_version in \"2.7 3.2 3.3\"").unwrap();
assert!(marker.evaluate(&env27, None, &[])); assert!(marker.evaluate(&env27, MarkerVariantsUniversal, &[]));
assert!(!marker.evaluate(&env37, None, &[])); assert!(!marker.evaluate(&env37, MarkerVariantsUniversal, &[]));
let marker = MarkerTree::from_str("python_version in \"2.7 3.7\"").unwrap(); let marker = MarkerTree::from_str("python_version in \"2.7 3.7\"").unwrap();
assert!(marker.evaluate(&env27, None, &[])); assert!(marker.evaluate(&env27, MarkerVariantsUniversal, &[]));
assert!(marker.evaluate(&env37, None, &[])); assert!(marker.evaluate(&env37, MarkerVariantsUniversal, &[]));
let marker = MarkerTree::from_str("python_version in \"2.4 3.8 4.0\"").unwrap(); let marker = MarkerTree::from_str("python_version in \"2.4 3.8 4.0\"").unwrap();
assert!(!marker.evaluate(&env27, None, &[])); assert!(!marker.evaluate(&env27, MarkerVariantsUniversal, &[]));
assert!(!marker.evaluate(&env37, None, &[])); assert!(!marker.evaluate(&env37, MarkerVariantsUniversal, &[]));
let marker = MarkerTree::from_str("python_version not in \"2.7 3.2 3.3\"").unwrap(); let marker = MarkerTree::from_str("python_version not in \"2.7 3.2 3.3\"").unwrap();
assert!(!marker.evaluate(&env27, None, &[])); assert!(!marker.evaluate(&env27, MarkerVariantsUniversal, &[]));
assert!(marker.evaluate(&env37, None, &[])); assert!(marker.evaluate(&env37, MarkerVariantsUniversal, &[]));
let marker = MarkerTree::from_str("python_version not in \"2.7 3.7\"").unwrap(); let marker = MarkerTree::from_str("python_version not in \"2.7 3.7\"").unwrap();
assert!(!marker.evaluate(&env27, None, &[])); assert!(!marker.evaluate(&env27, MarkerVariantsUniversal, &[]));
assert!(!marker.evaluate(&env37, None, &[])); assert!(!marker.evaluate(&env37, MarkerVariantsUniversal, &[]));
let marker = MarkerTree::from_str("python_version not in \"2.4 3.8 4.0\"").unwrap(); let marker = MarkerTree::from_str("python_version not in \"2.4 3.8 4.0\"").unwrap();
assert!(marker.evaluate(&env27, None, &[])); assert!(marker.evaluate(&env27, MarkerVariantsUniversal, &[]));
assert!(marker.evaluate(&env37, None, &[])); assert!(marker.evaluate(&env37, MarkerVariantsUniversal, &[]));
let marker = MarkerTree::from_str("python_full_version in \"2.7\"").unwrap(); let marker = MarkerTree::from_str("python_full_version in \"2.7\"").unwrap();
assert!(marker.evaluate(&env27, None, &[])); assert!(marker.evaluate(&env27, MarkerVariantsUniversal, &[]));
assert!(!marker.evaluate(&env37, None, &[])); assert!(!marker.evaluate(&env37, MarkerVariantsUniversal, &[]));
let marker = MarkerTree::from_str("implementation_version in \"2.7 3.2 3.3\"").unwrap(); let marker = MarkerTree::from_str("implementation_version in \"2.7 3.2 3.3\"").unwrap();
assert!(marker.evaluate(&env27, None, &[])); assert!(marker.evaluate(&env27, MarkerVariantsUniversal, &[]));
assert!(!marker.evaluate(&env37, None, &[])); assert!(!marker.evaluate(&env37, MarkerVariantsUniversal, &[]));
let marker = MarkerTree::from_str("implementation_version in \"2.7 3.7\"").unwrap(); let marker = MarkerTree::from_str("implementation_version in \"2.7 3.7\"").unwrap();
assert!(marker.evaluate(&env27, None, &[])); assert!(marker.evaluate(&env27, MarkerVariantsUniversal, &[]));
assert!(marker.evaluate(&env37, None, &[])); assert!(marker.evaluate(&env37, MarkerVariantsUniversal, &[]));
let marker = MarkerTree::from_str("implementation_version not in \"2.7 3.7\"").unwrap(); let marker = MarkerTree::from_str("implementation_version not in \"2.7 3.7\"").unwrap();
assert!(!marker.evaluate(&env27, None, &[])); assert!(!marker.evaluate(&env27, MarkerVariantsUniversal, &[]));
assert!(!marker.evaluate(&env37, None, &[])); assert!(!marker.evaluate(&env37, MarkerVariantsUniversal, &[]));
let marker = MarkerTree::from_str("implementation_version not in \"2.4 3.8 4.0\"").unwrap(); let marker = MarkerTree::from_str("implementation_version not in \"2.4 3.8 4.0\"").unwrap();
assert!(marker.evaluate(&env27, None, &[])); assert!(marker.evaluate(&env27, MarkerVariantsUniversal, &[]));
assert!(marker.evaluate(&env37, None, &[])); assert!(marker.evaluate(&env37, MarkerVariantsUniversal, &[]));
} }
#[test] #[test]
@ -2291,7 +2335,7 @@ mod test {
fn warnings1() { fn warnings1() {
let env37 = env37(); let env37 = env37();
let compare_keys = MarkerTree::from_str("platform_version == sys_platform").unwrap(); let compare_keys = MarkerTree::from_str("platform_version == sys_platform").unwrap();
compare_keys.evaluate(&env37, None, &[]); compare_keys.evaluate(&env37, MarkerVariantsUniversal, &[]);
logs_contain( logs_contain(
"Comparing two markers with each other doesn't make any sense, will evaluate to false", "Comparing two markers with each other doesn't make any sense, will evaluate to false",
); );
@ -2303,7 +2347,7 @@ mod test {
fn warnings2() { fn warnings2() {
let env37 = env37(); let env37 = env37();
let non_pep440 = MarkerTree::from_str("python_version >= '3.9.'").unwrap(); let non_pep440 = MarkerTree::from_str("python_version >= '3.9.'").unwrap();
non_pep440.evaluate(&env37, None, &[]); non_pep440.evaluate(&env37, MarkerVariantsUniversal, &[]);
logs_contain( logs_contain(
"Expected PEP 440 version to compare with python_version, found `3.9.`, \ "Expected PEP 440 version to compare with python_version, found `3.9.`, \
will evaluate to false: after parsing `3.9`, found `.`, which is \ will evaluate to false: after parsing `3.9`, found `.`, which is \
@ -2317,7 +2361,7 @@ mod test {
fn warnings3() { fn warnings3() {
let env37 = env37(); let env37 = env37();
let string_string = MarkerTree::from_str("'b' >= 'a'").unwrap(); let string_string = MarkerTree::from_str("'b' >= 'a'").unwrap();
string_string.evaluate(&env37, None, &[]); string_string.evaluate(&env37, MarkerVariantsUniversal, &[]);
logs_contain( logs_contain(
"Comparing two quoted strings with each other doesn't make sense: 'b' >= 'a', will evaluate to false", "Comparing two quoted strings with each other doesn't make sense: 'b' >= 'a', will evaluate to false",
); );
@ -2329,7 +2373,7 @@ mod test {
fn warnings4() { fn warnings4() {
let env37 = env37(); 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(); 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, None, &[]); string_string.evaluate(&env37, MarkerVariantsUniversal, &[]);
logs_assert(|lines: &[&str]| { logs_assert(|lines: &[&str]| {
let lines: Vec<_> = lines let lines: Vec<_> = lines
.iter() .iter()
@ -2361,18 +2405,18 @@ mod test {
let env37 = env37(); let env37 = env37();
let result = MarkerTree::from_str("python_version > '3.6'") let result = MarkerTree::from_str("python_version > '3.6'")
.unwrap() .unwrap()
.evaluate(&env37, None, &[]); .evaluate(&env37, MarkerVariantsUniversal, &[]);
assert!(result); assert!(result);
let result = MarkerTree::from_str("'3.6' > python_version") let result = MarkerTree::from_str("'3.6' > python_version")
.unwrap() .unwrap()
.evaluate(&env37, None, &[]); .evaluate(&env37, MarkerVariantsUniversal, &[]);
assert!(!result); assert!(!result);
// Meaningless expressions are ignored, so this is always true. // Meaningless expressions are ignored, so this is always true.
let result = MarkerTree::from_str("'3.*' == python_version") let result = MarkerTree::from_str("'3.*' == python_version")
.unwrap() .unwrap()
.evaluate(&env37, None, &[]); .evaluate(&env37, MarkerVariantsUniversal, &[]);
assert!(result); assert!(result);
} }
@ -2381,12 +2425,12 @@ mod test {
let env37 = env37(); let env37 = env37();
let result = MarkerTree::from_str("'nux' in sys_platform") let result = MarkerTree::from_str("'nux' in sys_platform")
.unwrap() .unwrap()
.evaluate(&env37, None, &[]); .evaluate(&env37, MarkerVariantsUniversal, &[]);
assert!(result); assert!(result);
let result = MarkerTree::from_str("sys_platform in 'nux'") let result = MarkerTree::from_str("sys_platform in 'nux'")
.unwrap() .unwrap()
.evaluate(&env37, None, &[]); .evaluate(&env37, MarkerVariantsUniversal, &[]);
assert!(!result); assert!(!result);
} }
@ -2395,7 +2439,7 @@ mod test {
let env37 = env37(); let env37 = env37();
let result = MarkerTree::from_str("python_version == '3.7.*'") let result = MarkerTree::from_str("python_version == '3.7.*'")
.unwrap() .unwrap()
.evaluate(&env37, None, &[]); .evaluate(&env37, MarkerVariantsUniversal, &[]);
assert!(result); assert!(result);
} }
@ -2404,7 +2448,7 @@ mod test {
let env37 = env37(); let env37 = env37();
let result = MarkerTree::from_str("python_version ~= '3.7'") let result = MarkerTree::from_str("python_version ~= '3.7'")
.unwrap() .unwrap()
.evaluate(&env37, None, &[]); .evaluate(&env37, MarkerVariantsUniversal, &[]);
assert!(result); assert!(result);
} }
@ -3754,68 +3798,69 @@ mod test {
#[test] #[test]
fn marker_evaluation_variants() { fn marker_evaluation_variants() {
let env37 = env37(); let env37 = env37();
let gpu_namespaces = [("gpu".to_string(), "cuda".to_string(), "12.4".to_string())]; let gpu_namespaces = [("gpu", "cuda", "12.4")].as_slice();
let cpu_namespaces = [("cpu".to_string(), String::new(), String::new())]; let cpu_namespaces = [("cpu", "", "")].as_slice();
// namespace variant markers // namespace variant markers
let marker1 = m("'gpu' in variant_namespaces"); let marker1 = m("'gpu' in variant_namespaces");
let marker2 = m("'gpu' not in variant_namespaces"); let marker2 = m("'gpu' not in variant_namespaces");
// If no variants are provided, we solve universally. // If no variants are provided, we solve universally.
assert!(marker1.evaluate(&env37, None, &[])); assert!(marker1.evaluate(&env37, MarkerVariantsUniversal, &[]));
assert!(marker2.evaluate(&env37, None, &[])); assert!(marker2.evaluate(&env37, MarkerVariantsUniversal, &[]));
assert!(marker1.evaluate(&env37, Some(&gpu_namespaces), &[])); assert!(marker1.evaluate(&env37, gpu_namespaces, &[]));
assert!(!marker1.evaluate(&env37, Some(&cpu_namespaces), &[])); assert!(!marker1.evaluate(&env37, cpu_namespaces, &[]));
assert!(!marker2.evaluate(&env37, Some(&gpu_namespaces), &[])); assert!(!marker2.evaluate(&env37, gpu_namespaces, &[]));
assert!(marker2.evaluate(&env37, Some(&cpu_namespaces), &[])); assert!(marker2.evaluate(&env37, cpu_namespaces, &[]));
// property variant markers // property variant markers
let marker3 = m("'gpu :: cuda' in variant_features"); let marker3 = m("'gpu :: cuda' in variant_features");
let marker4 = m("'gpu :: rocm' in variant_features"); let marker4 = m("'gpu :: rocm' in variant_features");
assert!(marker3.evaluate(&env37, None, &[])); assert!(marker3.evaluate(&env37, MarkerVariantsUniversal, &[]));
assert!(marker4.evaluate(&env37, None, &[])); assert!(marker4.evaluate(&env37, MarkerVariantsUniversal, &[]));
assert!(marker3.evaluate(&env37, Some(&gpu_namespaces), &[])); assert!(marker3.evaluate(&env37, gpu_namespaces, &[]));
assert!(!marker3.evaluate(&env37, Some(&cpu_namespaces), &[])); assert!(!marker3.evaluate(&env37, cpu_namespaces, &[]));
assert!(!marker4.evaluate(&env37, Some(&gpu_namespaces), &[])); assert!(!marker4.evaluate(&env37, gpu_namespaces, &[]));
assert!(!marker4.evaluate(&env37, Some(&cpu_namespaces), &[])); assert!(!marker4.evaluate(&env37, cpu_namespaces, &[]));
// feature variant markers // feature variant markers
let marker5 = m("'gpu :: cuda :: 12.4' in variant_properties"); let marker5 = m("'gpu :: cuda :: 12.4' in variant_properties");
let marker6 = m("'gpu :: cuda :: 12.8' in variant_properties"); let marker6 = m("'gpu :: cuda :: 12.8' in variant_properties");
assert!(marker5.evaluate(&env37, None, &[])); assert!(marker5.evaluate(&env37, MarkerVariantsUniversal, &[]));
assert!(marker6.evaluate(&env37, None, &[])); assert!(marker6.evaluate(&env37, MarkerVariantsUniversal, &[]));
assert!(marker5.evaluate(&env37, Some(&gpu_namespaces), &[])); assert!(marker5.evaluate(&env37, gpu_namespaces, &[]));
assert!(!marker5.evaluate(&env37, Some(&cpu_namespaces), &[])); assert!(!marker5.evaluate(&env37, cpu_namespaces, &[]));
assert!(!marker6.evaluate(&env37, Some(&gpu_namespaces), &[])); assert!(!marker6.evaluate(&env37, gpu_namespaces, &[]));
assert!(!marker6.evaluate(&env37, Some(&cpu_namespaces), &[])); assert!(!marker6.evaluate(&env37, cpu_namespaces, &[]));
} }
#[test] #[test]
fn marker_evaluation_variants_combined() { fn marker_evaluation_variants_combined() {
let env37 = env37(); let env37 = env37();
let namespaces = [ let namespaces = [
("gpu".to_string(), "cuda".to_string(), "12.4".to_string()), ("gpu", "cuda", "12.4"),
("gpu".to_string(), "cuda".to_string(), "12.6".to_string()), ("gpu", "cuda", "12.6"),
("cpu".to_string(), "x86_64".to_string(), "v1".to_string()), ("cpu", "x86_64", "v1"),
("cpu".to_string(), "x86_64".to_string(), "v2".to_string()), ("cpu", "x86_64", "v2"),
("cpu".to_string(), "x86_64".to_string(), "v3".to_string()), ("cpu", "x86_64", "v3"),
]; ]
.as_slice();
let marker1 = m("'gpu' in variant_namespaces \ let marker1 = m("'gpu' in variant_namespaces \
and 'cpu:: x86_64 :: v3' in variant_properties \ and 'cpu:: x86_64 :: v3' in variant_properties \
and python_version >= '3.7' \ and python_version >= '3.7' \
and 'gpu :: rocm' not in variant_features"); and 'gpu :: rocm' not in variant_features");
assert!(marker1.evaluate(&env37, None, &[])); assert!(marker1.evaluate(&env37, MarkerVariantsUniversal, &[]));
assert!(marker1.evaluate(&env37, Some(&namespaces), &[])); assert!(marker1.evaluate(&env37, namespaces, &[]));
let marker2 = m("python_version >= '3.7' and 'gpu' not in variant_namespaces"); let marker2 = m("python_version >= '3.7' and 'gpu' not in variant_namespaces");
assert!(marker2.evaluate(&env37, None, &[])); assert!(marker2.evaluate(&env37, MarkerVariantsUniversal, &[]));
assert!(!marker2.evaluate(&env37, Some(&namespaces), &[])); assert!(!marker2.evaluate(&env37, namespaces, &[]));
} }
#[test] #[test]
@ -3863,33 +3908,30 @@ mod test {
#[test] #[test]
fn variant_invalid() { fn variant_invalid() {
let env37 = env37(); let env37 = env37();
let namespaces = [("gpu".to_string(), "cuda".to_string(), "cuda126".to_string())]; let namespaces = [("gpu", "cuda", "cuda126")].as_slice();
let marker = m(r"'gpu :: cuda :: cuda126 :: gtx1080' in variant_properties"); let marker = m(r"'gpu :: cuda :: cuda126 :: gtx1080' in variant_properties");
assert!(!marker.evaluate(&env37, Some(&namespaces), &[])); assert!(!marker.evaluate(&env37, namespaces, &[]));
} }
#[test] #[test]
fn torch_variant_marker() { fn torch_variant_marker() {
let env37 = env37(); let env37 = env37();
let cu126 = [("nvidia".to_string(), "ctk".to_string(), "12.6".to_string())]; let cu126 = [("nvidia", "ctk", "12.6")].as_slice();
let cu126_2 = [ let cu126_2 = [
("nvidia".to_string(), "ctk".to_string(), "12.6".to_string()), ("nvidia", "ctk", "12.6"),
( ("nvidia", "cuda_version", ">=12.6,<13"),
"nvidia".to_string(), ]
"cuda_version".to_string(), .as_slice();
">=12.6,<13".to_string(), let cu128 = [("nvidia", "ctk", "12.8")].as_slice();
),
];
let cu128 = [("nvidia".to_string(), "ctk".to_string(), "12.8".to_string())];
let marker = m( let marker = m(
" platform_machine == 'x86_64' and sys_platform == 'linux' and 'nvidia :: ctk :: 12.6' in variant_properties", " 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, MarkerVariantsUniversal, &[]));
assert!(marker.evaluate(&env37, Some(&cu126), &[])); assert!(marker.evaluate(&env37, cu126, &[]));
assert!(marker.evaluate(&env37, Some(&cu126_2), &[])); assert!(marker.evaluate(&env37, cu126_2, &[]));
assert!(!marker.evaluate(&env37, Some(&cu128), &[])); assert!(!marker.evaluate(&env37, cu128, &[]));
} }
} }

View File

@ -8,9 +8,10 @@ use uv_normalize::ExtraName;
use crate::marker::parse; use crate::marker::parse;
use crate::{ use crate::{
Cursor, MarkerEnvironment, MarkerTree, Pep508Error, Pep508ErrorSource, Pep508Url, Reporter, Cursor, MarkerEnvironment, MarkerTree, MarkerVariantsEnvironment, Pep508Error,
RequirementOrigin, Scheme, TracingReporter, VerbatimUrl, VerbatimUrlError, expand_env_vars, Pep508ErrorSource, Pep508Url, Reporter, RequirementOrigin, Scheme, TracingReporter,
parse_extras_cursor, split_extras, split_scheme, strip_host, VerbatimUrl, VerbatimUrlError, expand_env_vars, parse_extras_cursor, split_extras,
split_scheme, strip_host,
}; };
/// An extension over [`Pep508Url`] that also supports parsing unnamed requirements, namely paths. /// An extension over [`Pep508Url`] that also supports parsing unnamed requirements, namely paths.
@ -85,7 +86,7 @@ impl<Url: UnnamedRequirementUrl> UnnamedRequirement<Url> {
pub fn evaluate_markers( pub fn evaluate_markers(
&self, &self,
env: &MarkerEnvironment, env: &MarkerEnvironment,
variants: Option<&[(String, String, String)]>, variants: impl MarkerVariantsEnvironment,
extras: &[ExtraName], extras: &[ExtraName],
) -> bool { ) -> bool {
self.evaluate_optional_environment(Some(env), variants, extras) self.evaluate_optional_environment(Some(env), variants, extras)
@ -95,7 +96,7 @@ impl<Url: UnnamedRequirementUrl> UnnamedRequirement<Url> {
pub fn evaluate_optional_environment( pub fn evaluate_optional_environment(
&self, &self,
env: Option<&MarkerEnvironment>, env: Option<&MarkerEnvironment>,
variants: Option<&[(String, String, String)]>, variants: impl MarkerVariantsEnvironment,
extras: &[ExtraName], extras: &[ExtraName],
) -> bool { ) -> bool {
self.marker self.marker

View File

@ -8,6 +8,7 @@ use tracing::trace;
use uv_configuration::{Constraints, Overrides}; use uv_configuration::{Constraints, Overrides};
use uv_distribution::{DistributionDatabase, Reporter}; use uv_distribution::{DistributionDatabase, Reporter};
use uv_distribution_types::{Dist, DistributionMetadata, Requirement, RequirementSource}; use uv_distribution_types::{Dist, DistributionMetadata, Requirement, RequirementSource};
use uv_pep508::MarkerVariantsUniversal;
use uv_resolver::{InMemoryIndex, MetadataResponse, ResolverEnvironment}; use uv_resolver::{InMemoryIndex, MetadataResponse, ResolverEnvironment};
use uv_types::{BuildContext, HashStrategy, RequestedRequirements}; use uv_types::{BuildContext, HashStrategy, RequestedRequirements};
@ -91,7 +92,9 @@ impl<'a, Context: BuildContext> LookaheadResolver<'a, Context> {
let mut queue: VecDeque<_> = self let mut queue: VecDeque<_> = self
.constraints .constraints
.apply(self.overrides.apply(self.requirements)) .apply(self.overrides.apply(self.requirements))
.filter(|requirement| requirement.evaluate_markers(env.marker_environment(), None, &[])) .filter(|requirement| {
requirement.evaluate_markers(env.marker_environment(), MarkerVariantsUniversal, &[])
})
.map(|requirement| (*requirement).clone()) .map(|requirement| (*requirement).clone())
.collect(); .collect();
@ -112,7 +115,7 @@ impl<'a, Context: BuildContext> LookaheadResolver<'a, Context> {
{ {
if requirement.evaluate_markers( if requirement.evaluate_markers(
env.marker_environment(), env.marker_environment(),
None, MarkerVariantsUniversal,
lookahead.extras(), lookahead.extras(),
) { ) {
queue.push_back((*requirement).clone()); queue.push_back((*requirement).clone());

View File

@ -16,7 +16,9 @@ use uv_distribution_types::{
}; };
use uv_normalize::{ExtraName, InvalidNameError, PackageName}; use uv_normalize::{ExtraName, InvalidNameError, PackageName};
use uv_pep440::{LocalVersionSlice, LowerBound, Version, VersionSpecifier}; use uv_pep440::{LocalVersionSlice, LowerBound, Version, VersionSpecifier};
use uv_pep508::{MarkerEnvironment, MarkerExpression, MarkerTree, MarkerValueVersion}; use uv_pep508::{
MarkerEnvironment, MarkerExpression, MarkerTree, MarkerValueVersion, MarkerVariantsUniversal,
};
use uv_platform_tags::Tags; use uv_platform_tags::Tags;
use uv_pypi_types::ParsedUrl; use uv_pypi_types::ParsedUrl;
use uv_redacted::DisplaySafeUrl; use uv_redacted::DisplaySafeUrl;
@ -412,7 +414,7 @@ impl NoSolutionError {
":".bold(), ":".bold(),
current_python_version, current_python_version,
)?; )?;
} else if !markers.evaluate(&self.current_environment, None, &[]) { } else if !markers.evaluate(&self.current_environment, MarkerVariantsUniversal, &[]) {
write!( write!(
f, f,
"\n\n{}{} The resolution failed for an environment that is not the current one, \ "\n\n{}{} The resolution failed for an environment that is not the current one, \

View File

@ -5,7 +5,7 @@ use petgraph::visit::EdgeRef;
use petgraph::{Direction, Graph}; use petgraph::{Direction, Graph};
use rustc_hash::{FxBuildHasher, FxHashMap, FxHashSet}; use rustc_hash::{FxBuildHasher, FxHashMap, FxHashSet};
use uv_pep508::MarkerTree; use uv_pep508::{MarkerTree, MarkerVariantsUniversal};
use uv_pypi_types::{ConflictItem, Conflicts}; use uv_pypi_types::{ConflictItem, Conflicts};
use crate::resolution::ResolutionGraphNode; use crate::resolution::ResolutionGraphNode;
@ -272,7 +272,7 @@ pub(crate) fn simplify_conflict_markers(
.collect::<Vec<_>>(); .collect::<Vec<_>>();
graph[edge_index] graph[edge_index]
.conflict() .conflict()
.evaluate(None, &extras, &groups) .evaluate(&extras, &groups, MarkerVariantsUniversal)
}); });
if !all_paths_satisfied { if !all_paths_satisfied {
continue; continue;

View File

@ -31,7 +31,7 @@ use uv_git::{RepositoryReference, ResolvedRepositoryReference};
use uv_git_types::{GitOid, GitReference, GitUrl, GitUrlParseError}; use uv_git_types::{GitOid, GitReference, GitUrl, GitUrlParseError};
use uv_normalize::{ExtraName, GroupName, PackageName}; use uv_normalize::{ExtraName, GroupName, PackageName};
use uv_pep440::Version; use uv_pep440::Version;
use uv_pep508::{MarkerEnvironment, MarkerTree, VerbatimUrl}; use uv_pep508::{MarkerEnvironment, MarkerTree, MarkerVariantsUniversal, VerbatimUrl};
use uv_platform_tags::{TagCompatibility, TagPriority, Tags}; use uv_platform_tags::{TagCompatibility, TagPriority, Tags};
use uv_pypi_types::{HashDigests, Hashes, ParsedGitUrl, VcsKind}; use uv_pypi_types::{HashDigests, Hashes, ParsedGitUrl, VcsKind};
use uv_redacted::DisplaySafeUrl; use uv_redacted::DisplaySafeUrl;
@ -982,7 +982,7 @@ impl<'lock> PylockToml {
// Omit packages that aren't relevant to the current environment. // Omit packages that aren't relevant to the current environment.
if !package if !package
.marker .marker
.evaluate_pep751(markers, None, extras, groups) .evaluate_pep751(markers, MarkerVariantsUniversal, extras, groups)
{ {
continue; continue;
} }

View File

@ -14,11 +14,12 @@ use uv_configuration::{BuildOptions, DependencyGroupsWithDefaults, InstallOption
use uv_distribution::{DistributionDatabase, VariantProviderCache}; use uv_distribution::{DistributionDatabase, VariantProviderCache};
use uv_distribution_types::{Edge, Identifier, Node, Resolution, ResolvedDist}; use uv_distribution_types::{Edge, Identifier, Node, Resolution, ResolvedDist};
use uv_normalize::{ExtraName, GroupName, PackageName}; use uv_normalize::{ExtraName, GroupName, PackageName};
use uv_pep508::MarkerVariantsUniversal;
use uv_platform_tags::Tags; use uv_platform_tags::Tags;
use uv_pypi_types::ResolverMarkerEnvironment; use uv_pypi_types::ResolverMarkerEnvironment;
use uv_types::BuildContext; use uv_types::BuildContext;
use uv_variants::score_variant; use uv_variants::score_variant;
use uv_variants::variants_json::VariantPropertyType; use uv_variants::variants_json::Variant;
use crate::lock::{LockErrorKind, Package, TagPolicy}; use crate::lock::{LockErrorKind, Package, TagPolicy};
use crate::{Lock, LockError}; use crate::{Lock, LockError};
@ -151,7 +152,7 @@ pub trait Installable<'lock> {
// TODO(konsti): Evaluate variant declarations on workspace/path dependencies. // TODO(konsti): Evaluate variant declarations on workspace/path dependencies.
if !dep.complexified_marker.evaluate( if !dep.complexified_marker.evaluate(
marker_env, marker_env,
None, MarkerVariantsUniversal,
activated_extras.iter().copied(), activated_extras.iter().copied(),
activated_groups.iter().copied(), activated_groups.iter().copied(),
) { ) {
@ -215,14 +216,17 @@ pub trait Installable<'lock> {
// Add any requirements that are exclusive to the workspace root (e.g., dependencies in // Add any requirements that are exclusive to the workspace root (e.g., dependencies in
// PEP 723 scripts). // PEP 723 scripts).
for dependency in self.lock().requirements() { for dependency in self.lock().requirements() {
if !dependency.marker.evaluate(marker_env, None, &[]) { if !dependency
.marker
.evaluate(marker_env, MarkerVariantsUniversal, &[])
{
continue; continue;
} }
let root_name = &dependency.name; let root_name = &dependency.name;
let dist = self let dist = self
.lock() .lock()
.find_by_markers(root_name, marker_env, None) .find_by_markers(root_name, marker_env)
.map_err(|_| LockErrorKind::MultipleRootPackages { .map_err(|_| LockErrorKind::MultipleRootPackages {
name: root_name.clone(), name: root_name.clone(),
})? })?
@ -267,7 +271,10 @@ pub trait Installable<'lock> {
}) })
.flatten() .flatten()
{ {
if !dependency.marker.evaluate(marker_env, None, &[]) { if !dependency
.marker
.evaluate(marker_env, MarkerVariantsUniversal, &[])
{
continue; continue;
} }
@ -275,7 +282,7 @@ pub trait Installable<'lock> {
// TODO(konsti): Evaluate variant declarations on workspace/path dependencies. // TODO(konsti): Evaluate variant declarations on workspace/path dependencies.
let dist = self let dist = self
.lock() .lock()
.find_by_markers(root_name, marker_env, None) .find_by_markers(root_name, marker_env)
.map_err(|_| LockErrorKind::MultipleRootPackages { .map_err(|_| LockErrorKind::MultipleRootPackages {
name: root_name.clone(), name: root_name.clone(),
})? })?
@ -378,7 +385,7 @@ pub trait Installable<'lock> {
// TODO(konsti): Evaluate variants // TODO(konsti): Evaluate variants
if !dep.complexified_marker.evaluate( if !dep.complexified_marker.evaluate(
marker_env, marker_env,
None, MarkerVariantsUniversal,
activated_extras activated_extras
.iter() .iter()
.chain(additional_activated_extras.iter()) .chain(additional_activated_extras.iter())
@ -475,7 +482,7 @@ pub trait Installable<'lock> {
for dep in deps { for dep in deps {
if !dep.complexified_marker.evaluate( if !dep.complexified_marker.evaluate(
marker_env, marker_env,
variant_properties.as_deref(), &variant_properties,
activated_extras.iter().copied(), activated_extras.iter().copied(),
activated_groups.iter().copied(), activated_groups.iter().copied(),
) { ) {
@ -600,9 +607,10 @@ async fn determine_properties<Context: BuildContext>(
workspace_root: &Path, workspace_root: &Path,
distribution_database: &DistributionDatabase<'_, Context>, distribution_database: &DistributionDatabase<'_, Context>,
variants_cache: &VariantProviderCache, variants_cache: &VariantProviderCache,
) -> Result<Option<Vec<(String, String, String)>>, LockError> { ) -> Result<Variant, LockError> {
let Some(variants_json) = package.to_registry_variants_json(workspace_root)? else { let Some(variants_json) = package.to_registry_variants_json(workspace_root)? else {
return Ok(None); // When selecting a non-variant wheel, all variant markers evaluate to false.
return Ok(Variant::default());
}; };
let resolved_variants = if let Some(resolved_variants) = let resolved_variants = if let Some(resolved_variants) =
variants_cache.get_resolved_variants(&variants_json.resource_id()) variants_cache.get_resolved_variants(&variants_json.resource_id())
@ -654,39 +662,15 @@ async fn determine_properties<Context: BuildContext>(
} }
if let Some((best_variant, _)) = highest_priority_variant_wheel { if let Some((best_variant, _)) = highest_priority_variant_wheel {
// TODO(konsti): Use the proper type everywhere, we shouldn't need to do // TODO(konsti): We shouldn't need to clone
// this conversion at all. let known_properties = resolved_variants
let mut known_properties = BTreeSet::new();
for (namespace, features) in resolved_variants
.variants_json .variants_json
.variants .variants
.get(best_variant) .get(best_variant)
.expect("TODO(konsti): error handling") .expect("TODO(konsti): error handling");
{ Ok(known_properties.clone())
for (feature, values) in features {
for value in values {
known_properties.insert(VariantPropertyType {
namespace: namespace.clone(),
feature: feature.clone(),
value: value.clone(),
});
}
}
}
let known_properties: Vec<(String, String, String)> = known_properties
.into_iter()
.map(|known_property| {
(
known_property.namespace,
known_property.feature,
known_property.value,
)
})
.collect();
Ok(Some(known_properties))
} else { } else {
// When selecting the non-variant wheel, all variant markers evaluate to // When selecting the non-variant wheel, all variant markers evaluate to false.
// false. Ok(Variant::default())
Ok(Some(Vec::new()))
} }
} }

View File

@ -1111,7 +1111,6 @@ impl Lock {
&self, &self,
name: &PackageName, name: &PackageName,
marker_env: &MarkerEnvironment, marker_env: &MarkerEnvironment,
variants: Option<&[(String, String, String)]>,
) -> Result<Option<&Package>, String> { ) -> Result<Option<&Package>, String> {
let mut found_dist = None; let mut found_dist = None;
for dist in &self.packages { for dist in &self.packages {
@ -1120,7 +1119,7 @@ impl Lock {
|| dist || dist
.fork_markers .fork_markers
.iter() .iter()
.any(|marker| marker.evaluate_no_extras(marker_env, variants)) .any(|marker| marker.evaluate_no_extras(marker_env))
{ {
if found_dist.is_some() { if found_dist.is_some() {
return Err(format!("found multiple packages matching `{name}`")); return Err(format!("found multiple packages matching `{name}`"));

View File

@ -12,7 +12,7 @@ use rustc_hash::{FxBuildHasher, FxHashMap, FxHashSet};
use uv_configuration::DependencyGroupsWithDefaults; use uv_configuration::DependencyGroupsWithDefaults;
use uv_normalize::{ExtraName, GroupName, PackageName}; use uv_normalize::{ExtraName, GroupName, PackageName};
use uv_pep440::Version; use uv_pep440::Version;
use uv_pep508::MarkerTree; use uv_pep508::{MarkerTree, MarkerVariantsUniversal};
use uv_pypi_types::ResolverMarkerEnvironment; use uv_pypi_types::ResolverMarkerEnvironment;
use crate::lock::PackageId; use crate::lock::PackageId;
@ -126,9 +126,9 @@ impl<'env> TreeDisplay<'env> {
continue; continue;
} }
if markers.is_some_and(|markers| { if markers
!dep.complexified_marker.evaluate_no_extras(markers, None) .is_some_and(|markers| !dep.complexified_marker.evaluate_no_extras(markers))
}) { {
continue; continue;
} }
@ -190,7 +190,9 @@ impl<'env> TreeDisplay<'env> {
if marker.is_false() { if marker.is_false() {
continue; continue;
} }
if markers.is_some_and(|markers| !marker.evaluate(markers, None, &[])) { if markers.is_some_and(|markers| {
!marker.evaluate(markers, MarkerVariantsUniversal, &[])
}) {
continue; continue;
} }
// Add the package to the graph. // Add the package to the graph.
@ -227,7 +229,9 @@ impl<'env> TreeDisplay<'env> {
if marker.is_false() { if marker.is_false() {
continue; continue;
} }
if markers.is_some_and(|markers| !marker.evaluate(markers, None, &[])) { if markers.is_some_and(|markers| {
!marker.evaluate(markers, MarkerVariantsUniversal, &[])
}) {
continue; continue;
} }
// Add the package to the graph. // Add the package to the graph.
@ -269,9 +273,9 @@ impl<'env> TreeDisplay<'env> {
continue; continue;
} }
if markers.is_some_and(|markers| { if markers
!dep.complexified_marker.evaluate_no_extras(markers, None) .is_some_and(|markers| !dep.complexified_marker.evaluate_no_extras(markers))
}) { {
continue; continue;
} }

View File

@ -6,6 +6,7 @@ use either::Either;
use uv_configuration::{Constraints, Overrides}; use uv_configuration::{Constraints, Overrides};
use uv_distribution_types::Requirement; use uv_distribution_types::Requirement;
use uv_normalize::PackageName; use uv_normalize::PackageName;
use uv_pep508::MarkerVariantsUniversal;
use uv_types::RequestedRequirements; use uv_types::RequestedRequirements;
use crate::preferences::Preferences; use crate::preferences::Preferences;
@ -116,43 +117,55 @@ impl Manifest {
) -> impl Iterator<Item = Cow<'a, Requirement>> + 'a { ) -> impl Iterator<Item = Cow<'a, Requirement>> + 'a {
match mode { match mode {
// Include all direct and transitive requirements, with constraints and overrides applied. // Include all direct and transitive requirements, with constraints and overrides applied.
DependencyMode::Transitive => Either::Left( DependencyMode::Transitive => {
self.lookaheads Either::Left(
.iter() self.lookaheads
.flat_map(move |lookahead| { .iter()
self.overrides .flat_map(move |lookahead| {
.apply(lookahead.requirements()) self.overrides.apply(lookahead.requirements()).filter(
.filter(move |requirement| { move |requirement| {
requirement.evaluate_markers(
env.marker_environment(),
MarkerVariantsUniversal,
lookahead.extras(),
)
},
)
})
.chain(self.overrides.apply(&self.requirements).filter(
move |requirement| {
requirement.evaluate_markers( requirement.evaluate_markers(
env.marker_environment(), env.marker_environment(),
None, MarkerVariantsUniversal,
lookahead.extras(), &[],
) )
}) },
}) ))
.chain( .chain(
self.overrides self.constraints
.apply(&self.requirements) .requirements()
.filter(move |requirement| { .filter(move |requirement| {
requirement.evaluate_markers(env.marker_environment(), None, &[]) requirement.evaluate_markers(
}), env.marker_environment(),
) MarkerVariantsUniversal,
.chain( &[],
self.constraints )
.requirements() })
.filter(move |requirement| { .map(Cow::Borrowed),
requirement.evaluate_markers(env.marker_environment(), None, &[]) ),
}) )
.map(Cow::Borrowed), }
),
),
// Include direct requirements, with constraints and overrides applied. // Include direct requirements, with constraints and overrides applied.
DependencyMode::Direct => Either::Right( DependencyMode::Direct => Either::Right(
self.overrides self.overrides
.apply(&self.requirements) .apply(&self.requirements)
.chain(self.constraints.requirements().map(Cow::Borrowed)) .chain(self.constraints.requirements().map(Cow::Borrowed))
.filter(move |requirement| { .filter(move |requirement| {
requirement.evaluate_markers(env.marker_environment(), None, &[]) requirement.evaluate_markers(
env.marker_environment(),
MarkerVariantsUniversal,
&[],
)
}), }),
), ),
} }
@ -170,7 +183,11 @@ impl Manifest {
self.overrides self.overrides
.requirements() .requirements()
.filter(move |requirement| { .filter(move |requirement| {
requirement.evaluate_markers(env.marker_environment(), None, &[]) requirement.evaluate_markers(
env.marker_environment(),
MarkerVariantsUniversal,
&[],
)
}) })
.map(Cow::Borrowed), .map(Cow::Borrowed),
), ),
@ -179,7 +196,11 @@ impl Manifest {
self.overrides self.overrides
.requirements() .requirements()
.filter(move |requirement| { .filter(move |requirement| {
requirement.evaluate_markers(env.marker_environment(), None, &[]) requirement.evaluate_markers(
env.marker_environment(),
MarkerVariantsUniversal,
&[],
)
}) })
.map(Cow::Borrowed), .map(Cow::Borrowed),
), ),
@ -214,7 +235,7 @@ impl Manifest {
move |requirement| { move |requirement| {
requirement.evaluate_markers( requirement.evaluate_markers(
env.marker_environment(), env.marker_environment(),
None, MarkerVariantsUniversal,
lookahead.extras(), lookahead.extras(),
) )
}, },
@ -222,7 +243,11 @@ impl Manifest {
}) })
.chain(self.overrides.apply(&self.requirements).filter( .chain(self.overrides.apply(&self.requirements).filter(
move |requirement| { move |requirement| {
requirement.evaluate_markers(env.marker_environment(), None, &[]) requirement.evaluate_markers(
env.marker_environment(),
MarkerVariantsUniversal,
&[],
)
}, },
)), )),
) )
@ -232,7 +257,11 @@ impl Manifest {
DependencyMode::Direct => { DependencyMode::Direct => {
Either::Right(self.overrides.apply(self.requirements.iter()).filter( Either::Right(self.overrides.apply(self.requirements.iter()).filter(
move |requirement| { move |requirement| {
requirement.evaluate_markers(env.marker_environment(), None, &[]) requirement.evaluate_markers(
env.marker_environment(),
MarkerVariantsUniversal,
&[],
)
}, },
)) ))
} }
@ -251,7 +280,7 @@ impl Manifest {
self.overrides self.overrides
.apply(self.requirements.iter()) .apply(self.requirements.iter())
.filter(move |requirement| { .filter(move |requirement| {
requirement.evaluate_markers(env.marker_environment(), None, &[]) requirement.evaluate_markers(env.marker_environment(), MarkerVariantsUniversal, &[])
}) })
} }

View File

@ -7,7 +7,7 @@ use tracing::trace;
use uv_distribution_types::{IndexUrl, InstalledDist}; use uv_distribution_types::{IndexUrl, InstalledDist};
use uv_normalize::PackageName; use uv_normalize::PackageName;
use uv_pep440::{Operator, Version}; use uv_pep440::{Operator, Version};
use uv_pep508::{MarkerTree, VerbatimUrl, VersionOrUrl}; use uv_pep508::{MarkerTree, MarkerVariantsUniversal, VerbatimUrl, VersionOrUrl};
use uv_pypi_types::{HashDigest, HashDigests, HashError}; use uv_pypi_types::{HashDigest, HashDigests, HashError};
use uv_requirements_txt::{RequirementEntry, RequirementsTxtRequirement}; use uv_requirements_txt::{RequirementEntry, RequirementsTxtRequirement};
@ -241,7 +241,10 @@ impl Preferences {
for preference in preferences { for preference in preferences {
// Filter non-matching preferences when resolving for an environment. // Filter non-matching preferences when resolving for an environment.
if let Some(markers) = env.marker_environment() { if let Some(markers) = env.marker_environment() {
if !preference.marker.evaluate(markers, None, &[]) { if !preference
.marker
.evaluate(markers, MarkerVariantsUniversal, &[])
{
trace!("Excluding {preference} from preferences due to unmatched markers"); trace!("Excluding {preference} from preferences due to unmatched markers");
continue; continue;
} }
@ -250,7 +253,7 @@ impl Preferences {
if !preference if !preference
.fork_markers .fork_markers
.iter() .iter()
.any(|marker| marker.evaluate_no_extras(markers, None)) .any(|marker| marker.evaluate_no_extras(markers))
{ {
trace!( trace!(
"Excluding {preference} from preferences due to unmatched fork markers" "Excluding {preference} from preferences due to unmatched fork markers"

View File

@ -7,7 +7,7 @@ use rustc_hash::{FxBuildHasher, FxHashMap};
use uv_distribution_types::{DistributionMetadata, Name, SourceAnnotation, SourceAnnotations}; use uv_distribution_types::{DistributionMetadata, Name, SourceAnnotation, SourceAnnotations};
use uv_normalize::PackageName; use uv_normalize::PackageName;
use uv_pep508::MarkerTree; use uv_pep508::{MarkerTree, MarkerVariantsUniversal};
use crate::resolution::{RequirementsTxtDist, ResolutionGraphNode}; use crate::resolution::{RequirementsTxtDist, ResolutionGraphNode};
use crate::{ResolverEnvironment, ResolverOutput}; use crate::{ResolverEnvironment, ResolverOutput};
@ -91,7 +91,11 @@ impl std::fmt::Display for DisplayResolutionGraph<'_> {
let mut sources = SourceAnnotations::default(); let mut sources = SourceAnnotations::default();
for requirement in self.resolution.requirements.iter().filter(|requirement| { for requirement in self.resolution.requirements.iter().filter(|requirement| {
requirement.evaluate_markers(self.env.marker_environment(), None, &[]) requirement.evaluate_markers(
self.env.marker_environment(),
MarkerVariantsUniversal,
&[],
)
}) { }) {
if let Some(origin) = &requirement.origin { if let Some(origin) = &requirement.origin {
sources.add( sources.add(
@ -106,7 +110,11 @@ impl std::fmt::Display for DisplayResolutionGraph<'_> {
.constraints .constraints
.requirements() .requirements()
.filter(|requirement| { .filter(|requirement| {
requirement.evaluate_markers(self.env.marker_environment(), None, &[]) requirement.evaluate_markers(
self.env.marker_environment(),
MarkerVariantsUniversal,
&[],
)
}) })
{ {
if let Some(origin) = &requirement.origin { if let Some(origin) = &requirement.origin {
@ -122,7 +130,11 @@ impl std::fmt::Display for DisplayResolutionGraph<'_> {
.overrides .overrides
.requirements() .requirements()
.filter(|requirement| { .filter(|requirement| {
requirement.evaluate_markers(self.env.marker_environment(), None, &[]) requirement.evaluate_markers(
self.env.marker_environment(),
MarkerVariantsUniversal,
&[],
)
}) })
{ {
if let Some(origin) = &requirement.origin { if let Some(origin) = &requirement.origin {

View File

@ -34,6 +34,7 @@ use uv_normalize::{ExtraName, GroupName, PackageName};
use uv_pep440::{MIN_VERSION, Version, VersionSpecifiers, release_specifiers_to_ranges}; use uv_pep440::{MIN_VERSION, Version, VersionSpecifiers, release_specifiers_to_ranges};
use uv_pep508::{ use uv_pep508::{
MarkerEnvironment, MarkerExpression, MarkerOperator, MarkerTree, MarkerValueString, MarkerEnvironment, MarkerExpression, MarkerOperator, MarkerTree, MarkerValueString,
MarkerVariantsEnvironment, MarkerVariantsUniversal,
}; };
use uv_platform_tags::Tags; use uv_platform_tags::Tags;
use uv_pypi_types::{ConflictItem, ConflictItemRef, Conflicts, VerbatimParsedUrl}; use uv_pypi_types::{ConflictItem, ConflictItemRef, Conflicts, VerbatimParsedUrl};
@ -84,7 +85,7 @@ use crate::{
pub(crate) use provider::MetadataUnavailable; pub(crate) use provider::MetadataUnavailable;
use uv_torch::TorchStrategy; use uv_torch::TorchStrategy;
use uv_variants::resolved_variants::ResolvedVariants; use uv_variants::resolved_variants::ResolvedVariants;
use uv_variants::variants_json::VariantPropertyType; use uv_variants::variants_json::Variant;
mod availability; mod availability;
mod batch_prefetch; mod batch_prefetch;
@ -1819,7 +1820,7 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
None, None,
None, None,
env, env,
None, &MarkerVariantsUniversal,
python_requirement, python_requirement,
); );
@ -1855,7 +1856,7 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
// If we're resolving for a specific environment, use the host variants, otherwise resolve // If we're resolving for a specific environment, use the host variants, otherwise resolve
// for all variants. // for all variants.
let variants = Self::variant_properties( let variant = Self::variant_properties(
name, name,
version, version,
&version_id, &version_id,
@ -1948,7 +1949,7 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
dev.as_ref(), dev.as_ref(),
Some(name), Some(name),
env, env,
variants.as_deref(), &variant,
python_requirement, python_requirement,
); );
@ -2047,55 +2048,37 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
pins: &FilePins, pins: &FilePins,
env: &ResolverEnvironment, env: &ResolverEnvironment,
in_memory_index: &InMemoryIndex, in_memory_index: &InMemoryIndex,
) -> Option<Vec<(String, String, String)>> { ) -> Variant {
// TODO(konsti): Perf/Caching with version selection: This is in the hot path! // TODO(konsti): Perf/Caching with version selection: This is in the hot path!
env.marker_environment()?; if env.marker_environment().is_none() {
let resolved_variants = in_memory_index return Variant::default();
}
let Some(resolved_variants) = in_memory_index
.variant_priorities() .variant_priorities()
.get(&(version_id.clone(), index_url.cloned()))?; .get(&(version_id.clone(), index_url.cloned()))
else {
return Variant::default();
};
let filename = pins.get(name, version).unwrap().wheel_filename(); let filename = pins.get(name, version).unwrap().wheel_filename();
let Some(filename) = filename else { let Some(filename) = filename else {
// TODO(konsti): Handle installed dists too // TODO(konsti): Handle installed dists too
return None; return Variant::default();
}; };
let Some(variant_label) = filename.variant() else { let Some(variant_label) = filename.variant() else {
warn!("Wheel variant has no associated variants: {filename}"); warn!("Wheel variant has no associated variants: {filename}");
return None; return Variant::default();
}; };
// Collect the host properties for marker filtering. // Collect the host properties for marker filtering.
// TODO(konsti): Use the proper type everywhere, we shouldn't need to do // TODO(konsti): We shouldn't need to clone
// this conversion at all. let variant = resolved_variants
let mut known_properties = BTreeSet::new();
let namespaces = resolved_variants
.variants_json .variants_json
.variants .variants
.get(variant_label) .get(variant_label)
.expect("Missing previously select variant label"); .expect("Missing previously select variant label");
for (namespace, features) in namespaces { variant.clone()
for (feature, values) in features {
for value in values {
known_properties.insert(VariantPropertyType {
namespace: namespace.clone(),
feature: feature.clone(),
value: value.clone(),
});
}
}
}
let known_properties: Vec<(String, String, String)> = known_properties
.into_iter()
.map(|known_property| {
(
known_property.namespace,
known_property.feature,
known_property.value,
)
})
.collect();
Some(known_properties)
} }
/// The regular and dev dependencies filtered by Python version and the markers of this fork, /// The regular and dev dependencies filtered by Python version and the markers of this fork,
@ -2109,7 +2092,7 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
dev: Option<&'a GroupName>, dev: Option<&'a GroupName>,
name: Option<&PackageName>, name: Option<&PackageName>,
env: &'a ResolverEnvironment, env: &'a ResolverEnvironment,
variants: Option<&'a [(String, String, String)]>, variants: &'a impl MarkerVariantsEnvironment,
python_requirement: &'a PythonRequirement, python_requirement: &'a PythonRequirement,
) -> impl Iterator<Item = Cow<'a, Requirement>> { ) -> impl Iterator<Item = Cow<'a, Requirement>> {
let python_marker = python_requirement.to_marker_tree(); let python_marker = python_requirement.to_marker_tree();
@ -2166,7 +2149,7 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
dependencies, dependencies,
Some(&extra), Some(&extra),
env, env,
variants, &variants,
python_marker, python_marker,
python_requirement, python_requirement,
) { ) {
@ -2236,7 +2219,7 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
dependencies: impl IntoIterator<Item = &'data Requirement> + 'parameters, dependencies: impl IntoIterator<Item = &'data Requirement> + 'parameters,
extra: Option<&'parameters ExtraName>, extra: Option<&'parameters ExtraName>,
env: &'parameters ResolverEnvironment, env: &'parameters ResolverEnvironment,
variants: Option<&'parameters [(String, String, String)]>, variants: &'parameters impl MarkerVariantsEnvironment,
python_marker: MarkerTree, python_marker: MarkerTree,
python_requirement: &'parameters PythonRequirement, python_requirement: &'parameters PythonRequirement,
) -> impl Iterator<Item = Cow<'data, Requirement>> + 'parameters ) -> impl Iterator<Item = Cow<'data, Requirement>> + 'parameters
@ -2273,7 +2256,7 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
requirement: &Requirement, requirement: &Requirement,
extra: Option<&ExtraName>, extra: Option<&ExtraName>,
env: &ResolverEnvironment, env: &ResolverEnvironment,
variants: Option<&[(String, String, String)]>, variants: impl MarkerVariantsEnvironment,
python_marker: MarkerTree, python_marker: MarkerTree,
python_requirement: &PythonRequirement, python_requirement: &PythonRequirement,
) -> bool { ) -> bool {
@ -2281,12 +2264,12 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
match extra { match extra {
Some(source_extra) => { Some(source_extra) => {
// Only include requirements that are relevant for the current extra. // Only include requirements that are relevant for the current extra.
if requirement.evaluate_markers(env.marker_environment(), variants, &[]) { if requirement.evaluate_markers(env.marker_environment(), &variants, &[]) {
return false; return false;
} }
if !requirement.evaluate_markers( if !requirement.evaluate_markers(
env.marker_environment(), env.marker_environment(),
variants, &variants,
slice::from_ref(source_extra), slice::from_ref(source_extra),
) { ) {
return false; return false;
@ -2330,7 +2313,7 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
requirement: Cow<'data, Requirement>, requirement: Cow<'data, Requirement>,
extra: Option<&'parameters ExtraName>, extra: Option<&'parameters ExtraName>,
env: &'parameters ResolverEnvironment, env: &'parameters ResolverEnvironment,
variants: Option<&'parameters [(String, String, String)]>, variants: impl MarkerVariantsEnvironment + 'parameters,
python_marker: MarkerTree, python_marker: MarkerTree,
python_requirement: &'parameters PythonRequirement, python_requirement: &'parameters PythonRequirement,
) -> impl Iterator<Item = Cow<'data, Requirement>> + 'parameters ) -> impl Iterator<Item = Cow<'data, Requirement>> + 'parameters
@ -2422,7 +2405,7 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
match extra { match extra {
Some(source_extra) => { Some(source_extra) => {
if !constraint if !constraint
.evaluate_markers(env.marker_environment(), variants, slice::from_ref(source_extra)) .evaluate_markers(env.marker_environment(), &variants, slice::from_ref(source_extra))
{ {
return None; return None;
} }
@ -2432,7 +2415,7 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
} }
} }
None => { None => {
if !constraint.evaluate_markers(env.marker_environment(), variants, &[]) { if !constraint.evaluate_markers(env.marker_environment(), &variants, &[]) {
return None; return None;
} }
} }

View File

@ -7,7 +7,7 @@ use rustc_hash::FxHashMap;
use uv_normalize::{ExtraName, GroupName, PackageName}; use uv_normalize::{ExtraName, GroupName, PackageName};
use uv_pep508::{ use uv_pep508::{
ExtraOperator, MarkerEnvironment, MarkerEnvironmentBuilder, MarkerExpression, MarkerOperator, ExtraOperator, MarkerEnvironment, MarkerEnvironmentBuilder, MarkerExpression, MarkerOperator,
MarkerTree, MarkerTree, MarkerVariantsEnvironment, MarkerVariantsUniversal,
}; };
use uv_pypi_types::{ConflictItem, ConflictPackage, Conflicts}; use uv_pypi_types::{ConflictItem, ConflictPackage, Conflicts};
@ -239,12 +239,8 @@ impl UniversalMarker {
/// ///
/// This should only be used when evaluating a marker that is known not to /// This should only be used when evaluating a marker that is known not to
/// have any extras. For example, the PEP 508 markers on a fork. /// have any extras. For example, the PEP 508 markers on a fork.
pub(crate) fn evaluate_no_extras( pub(crate) fn evaluate_no_extras(self, env: &MarkerEnvironment) -> bool {
self, self.marker.evaluate(env, MarkerVariantsUniversal, &[])
env: &MarkerEnvironment,
variants: Option<&[(String, String, String)]>,
) -> bool {
self.marker.evaluate(env, variants, &[])
} }
/// Returns true if this universal marker is satisfied by the given marker /// Returns true if this universal marker is satisfied by the given marker
@ -256,7 +252,7 @@ impl UniversalMarker {
pub(crate) fn evaluate<P, E, G>( pub(crate) fn evaluate<P, E, G>(
self, self,
env: &MarkerEnvironment, env: &MarkerEnvironment,
variants: Option<&[(String, String, String)]>, variants: impl MarkerVariantsEnvironment,
extras: impl Iterator<Item = (P, E)>, extras: impl Iterator<Item = (P, E)>,
groups: impl Iterator<Item = (P, G)>, groups: impl Iterator<Item = (P, G)>,
) -> bool ) -> bool
@ -436,9 +432,9 @@ impl ConflictMarker {
/// list of activated extras and groups. /// list of activated extras and groups.
pub(crate) fn evaluate<P, E, G>( pub(crate) fn evaluate<P, E, G>(
self, self,
variants: Option<&[(String, String, String)]>,
extras: &[(P, E)], extras: &[(P, E)],
groups: &[(P, G)], groups: &[(P, G)],
variants: impl MarkerVariantsEnvironment,
) -> bool ) -> bool
where where
P: Borrow<PackageName>, P: Borrow<PackageName>,
@ -745,7 +741,7 @@ pub(crate) fn resolve_conflicts(
mod tests { mod tests {
use super::*; use super::*;
use std::str::FromStr; use std::str::FromStr;
use uv_pep508::MarkerVariantsUniversal;
use uv_pypi_types::ConflictSet; use uv_pypi_types::ConflictSet;
/// Creates a collection of declared conflicts from the sets /// Creates a collection of declared conflicts from the sets
@ -875,7 +871,7 @@ mod tests {
.collect::<Vec<(PackageName, ExtraName)>>(); .collect::<Vec<(PackageName, ExtraName)>>();
let groups = Vec::<(PackageName, GroupName)>::new(); let groups = Vec::<(PackageName, GroupName)>::new();
assert!( assert!(
!cm.evaluate(None, &extras, &groups), !cm.evaluate(&extras, &groups, MarkerVariantsUniversal),
"expected `{extra_names:?}` to evaluate to `false` in `{cm:?}`" "expected `{extra_names:?}` to evaluate to `false` in `{cm:?}`"
); );
} }
@ -898,7 +894,7 @@ mod tests {
.collect::<Vec<(PackageName, ExtraName)>>(); .collect::<Vec<(PackageName, ExtraName)>>();
let groups = Vec::<(PackageName, GroupName)>::new(); let groups = Vec::<(PackageName, GroupName)>::new();
assert!( assert!(
cm.evaluate(None, &extras, &groups), cm.evaluate(&extras, &groups, MarkerVariantsUniversal),
"expected `{extra_names:?}` to evaluate to `true` in `{cm:?}`" "expected `{extra_names:?}` to evaluate to `true` in `{cm:?}`"
); );
} }

View File

@ -10,6 +10,7 @@ use uv_distribution_types::{
}; };
use uv_normalize::PackageName; use uv_normalize::PackageName;
use uv_pep440::Version; use uv_pep440::Version;
use uv_pep508::MarkerVariantsUniversal;
use uv_pypi_types::{HashDigest, HashDigests, HashError, ResolverMarkerEnvironment}; use uv_pypi_types::{HashDigest, HashDigests, HashError, ResolverMarkerEnvironment};
use uv_redacted::DisplaySafeUrl; use uv_redacted::DisplaySafeUrl;
@ -136,7 +137,7 @@ impl HashStrategy {
for (requirement, digests) in constraints { for (requirement, digests) in constraints {
if !requirement.evaluate_markers( if !requirement.evaluate_markers(
marker_env.map(ResolverMarkerEnvironment::markers), marker_env.map(ResolverMarkerEnvironment::markers),
None, MarkerVariantsUniversal,
&[], &[],
) { ) {
continue; continue;
@ -182,7 +183,7 @@ impl HashStrategy {
for (requirement, digests) in requirements { for (requirement, digests) in requirements {
if !requirement.evaluate_markers( if !requirement.evaluate_markers(
marker_env.map(ResolverMarkerEnvironment::markers), marker_env.map(ResolverMarkerEnvironment::markers),
None, MarkerVariantsUniversal,
&[], &[],
) { ) {
continue; continue;

View File

@ -39,7 +39,7 @@ pub fn score_variant(
target_namespaces: &FxHashMap<VariantNamespace, VariantProviderOutput>, target_namespaces: &FxHashMap<VariantNamespace, VariantProviderOutput>,
variants_properties: &Variant, variants_properties: &Variant,
) -> Option<Vec<usize>> { ) -> Option<Vec<usize>> {
for (namespace, features) in variants_properties { for (namespace, features) in &**variants_properties {
for (feature, properties) in features { for (feature, properties) in features {
let resolved_properties = target_namespaces let resolved_properties = target_namespaces
.get(namespace) .get(namespace)

View File

@ -1,18 +1,60 @@
use std::ops::Deref;
use indoc::formatdoc; use indoc::formatdoc;
use rustc_hash::FxHashMap; use rustc_hash::FxHashMap;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use uv_pep508::Requirement; use uv_pep508::{MarkerVariantsEnvironment, Requirement};
use uv_pypi_types::VerbatimParsedUrl; use uv_pypi_types::VerbatimParsedUrl;
/// Mapping of namespaces in a variant
pub type Variant = FxHashMap<String, FxHashMap<String, Vec<String>>>;
// TODO(konsti): Validate the string contents // TODO(konsti): Validate the string contents
pub type VariantNamespace = String; pub type VariantNamespace = String;
pub type VariantFeature = String; pub type VariantFeature = String;
pub type VariantProperty = String; pub type VariantProperty = String;
/// Mapping of namespaces in a variant
#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(transparent)]
pub struct Variant(FxHashMap<String, FxHashMap<String, Vec<String>>>);
impl MarkerVariantsEnvironment for Variant {
fn has_namespace(&self, namespace: &str) -> bool {
self.0.contains_key(namespace)
}
fn has_feature(&self, namespace: &str, feature: &str) -> bool {
let Some(features) = self.0.get(namespace) else {
return false;
};
let Some(properties) = features.get(feature) else {
return false;
};
!properties.is_empty()
}
fn has_property(&self, namespace: &str, feature: &str, property: &str) -> bool {
let Some(features) = self.0.get(namespace) else {
return false;
};
let Some(properties) = features.get(feature) else {
return false;
};
properties.iter().any(|p| p == property)
}
}
impl Deref for Variant {
type Target = FxHashMap<String, FxHashMap<String, Vec<String>>>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
/// Combined index metadata for wheel variants. /// Combined index metadata for wheel variants.
/// ///
/// See <https://wheelnext.dev/variants.json> /// See <https://wheelnext.dev/variants.json>

View File

@ -13,6 +13,7 @@ use uv_fs::Simplified;
use uv_install_wheel::read_record_file; use uv_install_wheel::read_record_file;
use uv_installer::SitePackages; use uv_installer::SitePackages;
use uv_normalize::PackageName; use uv_normalize::PackageName;
use uv_pep508::MarkerVariantsUniversal;
use uv_python::{EnvironmentPreference, PythonEnvironment, PythonRequest}; use uv_python::{EnvironmentPreference, PythonEnvironment, PythonRequest};
use crate::commands::ExitStatus; use crate::commands::ExitStatus;
@ -101,7 +102,7 @@ pub(crate) fn pip_show(
requires_map.insert( requires_map.insert(
dist.name(), dist.name(),
Box::into_iter(metadata.requires_dist) Box::into_iter(metadata.requires_dist)
.filter(|req| req.evaluate_markers(&markers, None, &[])) .filter(|req| req.evaluate_markers(&markers, MarkerVariantsUniversal, &[]))
.map(|req| req.name) .map(|req| req.name)
.sorted_unstable() .sorted_unstable()
.dedup() .dedup()
@ -117,7 +118,7 @@ pub(crate) fn pip_show(
} }
if let Ok(metadata) = installed.metadata() { if let Ok(metadata) = installed.metadata() {
let requires = Box::into_iter(metadata.requires_dist) let requires = Box::into_iter(metadata.requires_dist)
.filter(|req| req.evaluate_markers(&markers, None, &[])) .filter(|req| req.evaluate_markers(&markers, MarkerVariantsUniversal, &[]))
.map(|req| req.name) .map(|req| req.name)
.collect_vec(); .collect_vec();
if !requires.is_empty() { if !requires.is_empty() {

View File

@ -18,7 +18,7 @@ use uv_distribution_types::{Diagnostic, IndexCapabilities, IndexLocations, Name,
use uv_installer::SitePackages; use uv_installer::SitePackages;
use uv_normalize::PackageName; use uv_normalize::PackageName;
use uv_pep440::Version; use uv_pep440::Version;
use uv_pep508::{Requirement, VersionOrUrl}; use uv_pep508::{MarkerVariantsUniversal, Requirement, VersionOrUrl};
use uv_pypi_types::{ResolutionMetadata, ResolverMarkerEnvironment, VerbatimParsedUrl}; use uv_pypi_types::{ResolutionMetadata, ResolverMarkerEnvironment, VerbatimParsedUrl};
use uv_python::{EnvironmentPreference, PythonEnvironment, PythonRequest}; use uv_python::{EnvironmentPreference, PythonEnvironment, PythonRequest};
use uv_resolver::{ExcludeNewer, PrereleaseMode}; use uv_resolver::{ExcludeNewer, PrereleaseMode};
@ -254,7 +254,10 @@ impl<'env> DisplayDependencyGraph<'env> {
if prune.contains(&requirement.name) { if prune.contains(&requirement.name) {
continue; continue;
} }
if !requirement.marker.evaluate(markers, None, &[]) { if !requirement
.marker
.evaluate(markers, MarkerVariantsUniversal, &[])
{
continue; continue;
} }

View File

@ -25,7 +25,7 @@ use uv_distribution_types::{
use uv_fs::{PortablePathBuf, Simplified}; use uv_fs::{PortablePathBuf, Simplified};
use uv_installer::SitePackages; use uv_installer::SitePackages;
use uv_normalize::{DefaultExtras, DefaultGroups, PackageName}; use uv_normalize::{DefaultExtras, DefaultGroups, PackageName};
use uv_pep508::{MarkerTree, VersionOrUrl}; use uv_pep508::{MarkerTree, MarkerVariantsUniversal, VersionOrUrl};
use uv_pypi_types::{ParsedArchiveUrl, ParsedGitUrl, ParsedUrl}; use uv_pypi_types::{ParsedArchiveUrl, ParsedGitUrl, ParsedUrl};
use uv_python::{PythonDownloads, PythonEnvironment, PythonPreference, PythonRequest}; use uv_python::{PythonDownloads, PythonEnvironment, PythonPreference, PythonRequest};
use uv_resolver::{FlatIndex, Installable, Lock}; use uv_resolver::{FlatIndex, Installable, Lock};
@ -622,7 +622,7 @@ pub(super) async fn do_sync(
if !environments.is_empty() { if !environments.is_empty() {
if !environments if !environments
.iter() .iter()
.any(|env| env.evaluate(&marker_env, None, &[])) .any(|env| env.evaluate(&marker_env, MarkerVariantsUniversal, &[]))
{ {
return Err(ProjectError::LockedPlatformIncompatibility( return Err(ProjectError::LockedPlatformIncompatibility(
// For error reporting, we use the "simplified" // For error reporting, we use the "simplified"