diff --git a/crates/rattler_conda_types/src/match_spec/matcher.rs b/crates/rattler_conda_types/src/match_spec/matcher.rs index 7ff468a9f..2bb9ddef2 100644 --- a/crates/rattler_conda_types/src/match_spec/matcher.rs +++ b/crates/rattler_conda_types/src/match_spec/matcher.rs @@ -1,4 +1,5 @@ use serde::{Serialize, Serializer}; +use std::hash::{Hash, Hasher}; use std::{ fmt::{Display, Formatter}, str::FromStr, @@ -19,6 +20,16 @@ pub enum StringMatcher { Regex(regex::Regex), } +impl Hash for StringMatcher { + fn hash(&self, state: &mut H) { + match self { + StringMatcher::Exact(s) => s.hash(state), + StringMatcher::Glob(pattern) => pattern.hash(state), + StringMatcher::Regex(regex) => regex.as_str().hash(state), + } + } +} + impl PartialEq for StringMatcher { fn eq(&self, other: &Self) -> bool { match (self, other) { diff --git a/crates/rattler_conda_types/src/match_spec/mod.rs b/crates/rattler_conda_types/src/match_spec/mod.rs index 4b33d4fcc..3707b0f3b 100644 --- a/crates/rattler_conda_types/src/match_spec/mod.rs +++ b/crates/rattler_conda_types/src/match_spec/mod.rs @@ -3,6 +3,7 @@ use rattler_digest::{serde::SerializableHash, Md5Hash, Sha256Hash}; use serde::Serialize; use serde_with::{serde_as, skip_serializing_none}; use std::fmt::{Debug, Display, Formatter}; +use std::hash::Hash; pub mod matcher; pub mod parse; @@ -111,7 +112,7 @@ use matcher::StringMatcher; /// Alternatively, an exact spec is given by `*[sha256=01ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b]`. #[skip_serializing_none] #[serde_as] -#[derive(Debug, Default, Clone, Serialize, Eq, PartialEq)] +#[derive(Debug, Default, Clone, Serialize, Eq, PartialEq, Hash)] pub struct MatchSpec { /// The name of the package pub name: Option, @@ -351,6 +352,7 @@ mod tests { use rattler_digest::{parse_digest_from_hex, Md5, Sha256}; use crate::{MatchSpec, NamelessMatchSpec, PackageName, PackageRecord, Version}; + use std::hash::{Hash, Hasher}; #[test] fn test_matchspec_format_eq() { @@ -370,6 +372,34 @@ mod tests { assert_eq!(spec, rebuild_spec) } + #[test] + fn test_hash_match() { + let spec1 = MatchSpec::from_str("tensorflow 2.6.*").unwrap(); + let spec2 = MatchSpec::from_str("tensorflow 2.6.*").unwrap(); + assert_eq!(spec1, spec2); + + let mut hasher = std::collections::hash_map::DefaultHasher::new(); + let hash1 = spec1.hash(&mut hasher); + let hash2 = spec2.hash(&mut hasher); + + assert_eq!(hash1, hash2); + } + + #[test] + fn test_hash_no_match() { + let spec1 = MatchSpec::from_str("tensorflow 2.6.0.*").unwrap(); + let spec2 = MatchSpec::from_str("tensorflow 2.6.*").unwrap(); + assert_ne!(spec1, spec2); + + let mut hasher = std::collections::hash_map::DefaultHasher::new(); + spec1.hash(&mut hasher); + let hash1 = hasher.finish(); + spec2.hash(&mut hasher); + let hash2 = hasher.finish(); + + assert_ne!(hash1, hash2); + } + #[test] fn test_digest_match() { let record = PackageRecord { diff --git a/crates/rattler_conda_types/src/version/mod.rs b/crates/rattler_conda_types/src/version/mod.rs index 239f52fce..1d3d027bc 100644 --- a/crates/rattler_conda_types/src/version/mod.rs +++ b/crates/rattler_conda_types/src/version/mod.rs @@ -948,6 +948,46 @@ impl<'v> SegmentIter<'v> { } } +/// Version that only has equality when it is exactly the same +/// e.g for [`Version`] 1.0.0 == 1.0 while in [`StrictVersion`] +/// this is not equal. Useful in ranges where we are talking +/// about equality over version ranges instead of specific +/// version instances +#[derive(Clone, PartialOrd, Ord, Eq, Debug)] +pub struct StrictVersion(pub Version); + +impl PartialEq for StrictVersion { + fn eq(&self, other: &Self) -> bool { + // StrictVersion is only equal if the number + // of components are the same + // and the components are the same + self.0.components.len() == other.0.components.len() && self.0 == other.0 + } +} + +impl Display for StrictVersion { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.0) + } +} + +impl Hash for StrictVersion { + fn hash(&self, state: &mut H) { + fn hash_segments<'i, I: Iterator>, H: Hasher>( + state: &mut H, + segments: I, + ) { + for segment in segments { + segment.components().rev().for_each(|c| c.hash(state)); + } + } + + self.0.epoch().hash(state); + hash_segments(state, self.0.segments()); + hash_segments(state, self.0.local_segments()); + } +} + #[cfg(test)] mod test { use std::cmp::Ordering; @@ -958,6 +998,8 @@ mod test { use rand::seq::SliceRandom; + use crate::version::StrictVersion; + use super::Version; // Tests are inspired by: https://github.com/conda/conda/blob/33a142c16530fcdada6c377486f1c1a385738a96/tests/models/test_version.py @@ -1143,6 +1185,22 @@ mod test { assert_eq!(random_versions, parsed_versions); } + #[test] + fn strict_version_test() { + let v_1_0 = StrictVersion::from_str("1.0.0").unwrap(); + // Should be equal to itself + assert_eq!(v_1_0, v_1_0); + let v_1_0_0 = StrictVersion::from_str("1.0").unwrap(); + // Strict version should not discard zero's + assert_ne!(v_1_0, v_1_0_0); + // Ordering should stay the same as version + assert_eq!(v_1_0.cmp(&v_1_0_0), Ordering::Equal); + + // Hashing should consider v_1_0 and v_1_0_0 as unequal + assert_eq!(get_hash(&v_1_0), get_hash(&v_1_0)); + assert_ne!(get_hash(&v_1_0), get_hash(&v_1_0_0)); + } + #[test] fn bump() { assert_eq!( @@ -1166,7 +1224,7 @@ mod test { .starts_with(&Version::from_str("1.2").unwrap())); } - fn get_hash(spec: &Version) -> u64 { + fn get_hash(spec: &impl Hash) -> u64 { let mut s = DefaultHasher::new(); spec.hash(&mut s); s.finish() diff --git a/crates/rattler_conda_types/src/version/parse.rs b/crates/rattler_conda_types/src/version/parse.rs index 7f7d02da1..8f0bbe6f9 100644 --- a/crates/rattler_conda_types/src/version/parse.rs +++ b/crates/rattler_conda_types/src/version/parse.rs @@ -1,4 +1,4 @@ -use super::{Component, Version}; +use super::{Component, StrictVersion, Version}; use crate::version::flags::Flags; use crate::version::segment::Segment; use crate::version::{ComponentVec, SegmentVec}; @@ -437,6 +437,14 @@ impl FromStr for Version { } } +impl FromStr for StrictVersion { + type Err = ParseVersionError; + + fn from_str(s: &str) -> Result { + Ok(StrictVersion(Version::from_str(s)?)) + } +} + #[cfg(test)] mod test { use super::Version; diff --git a/crates/rattler_conda_types/src/version_spec/constraint.rs b/crates/rattler_conda_types/src/version_spec/constraint.rs index 889359751..a98137089 100644 --- a/crates/rattler_conda_types/src/version_spec/constraint.rs +++ b/crates/rattler_conda_types/src/version_spec/constraint.rs @@ -1,6 +1,7 @@ use super::ParseConstraintError; -use super::VersionOperator; +use super::RangeOperator; use crate::version_spec::parse::constraint_parser; +use crate::version_spec::{EqualityOperator, StrictRangeOperator}; use crate::Version; use std::str::FromStr; @@ -12,7 +13,13 @@ pub(crate) enum Constraint { Any, /// Version comparison (e.g `>1.2.3`) - Comparison(VersionOperator, Version), + Comparison(RangeOperator, Version), + + /// Strict comparison (e.g `~=1.2.3`) + StrictComparison(StrictRangeOperator, Version), + + /// Exact Version + Exact(EqualityOperator, Version), } /// Returns true if the specified character is the first character of a version constraint. @@ -37,7 +44,7 @@ impl FromStr for Constraint { mod test { use super::Constraint; use crate::version_spec::constraint::ParseConstraintError; - use crate::version_spec::VersionOperator; + use crate::version_spec::{EqualityOperator, RangeOperator, StrictRangeOperator}; use crate::Version; use std::str::FromStr; @@ -91,63 +98,63 @@ mod test { assert_eq!( Constraint::from_str(">1.2.3"), Ok(Constraint::Comparison( - VersionOperator::Greater, + RangeOperator::Greater, Version::from_str("1.2.3").unwrap() )) ); assert_eq!( Constraint::from_str("<1.2.3"), Ok(Constraint::Comparison( - VersionOperator::Less, + RangeOperator::Less, Version::from_str("1.2.3").unwrap() )) ); assert_eq!( Constraint::from_str("=1.2.3"), - Ok(Constraint::Comparison( - VersionOperator::StartsWith, + Ok(Constraint::StrictComparison( + StrictRangeOperator::StartsWith, Version::from_str("1.2.3").unwrap() )) ); assert_eq!( Constraint::from_str("==1.2.3"), - Ok(Constraint::Comparison( - VersionOperator::Equals, + Ok(Constraint::Exact( + EqualityOperator::Equals, Version::from_str("1.2.3").unwrap() )) ); assert_eq!( Constraint::from_str("!=1.2.3"), - Ok(Constraint::Comparison( - VersionOperator::NotEquals, + Ok(Constraint::Exact( + EqualityOperator::NotEquals, Version::from_str("1.2.3").unwrap() )) ); assert_eq!( Constraint::from_str("~=1.2.3"), - Ok(Constraint::Comparison( - VersionOperator::Compatible, + Ok(Constraint::StrictComparison( + StrictRangeOperator::Compatible, Version::from_str("1.2.3").unwrap() )) ); assert_eq!( Constraint::from_str(">=1.2.3"), Ok(Constraint::Comparison( - VersionOperator::GreaterEquals, + RangeOperator::GreaterEquals, Version::from_str("1.2.3").unwrap() )) ); assert_eq!( Constraint::from_str("<=1.2.3"), Ok(Constraint::Comparison( - VersionOperator::LessEquals, + RangeOperator::LessEquals, Version::from_str("1.2.3").unwrap() )) ); assert_eq!( Constraint::from_str(">=1!1.2"), Ok(Constraint::Comparison( - VersionOperator::GreaterEquals, + RangeOperator::GreaterEquals, Version::from_str("1!1.2").unwrap() )) ); @@ -157,50 +164,50 @@ mod test { fn test_glob_op() { assert_eq!( Constraint::from_str("=1.2.*"), - Ok(Constraint::Comparison( - VersionOperator::StartsWith, + Ok(Constraint::StrictComparison( + StrictRangeOperator::StartsWith, Version::from_str("1.2").unwrap() )) ); assert_eq!( Constraint::from_str("!=1.2.*"), - Ok(Constraint::Comparison( - VersionOperator::NotStartsWith, + Ok(Constraint::StrictComparison( + StrictRangeOperator::NotStartsWith, Version::from_str("1.2").unwrap() )) ); assert_eq!( Constraint::from_str(">=1.2.*"), Ok(Constraint::Comparison( - VersionOperator::GreaterEquals, + RangeOperator::GreaterEquals, Version::from_str("1.2").unwrap() )) ); assert_eq!( Constraint::from_str("==1.2.*"), - Ok(Constraint::Comparison( - VersionOperator::Equals, + Ok(Constraint::Exact( + EqualityOperator::Equals, Version::from_str("1.2").unwrap() )) ); assert_eq!( Constraint::from_str(">1.2.*"), Ok(Constraint::Comparison( - VersionOperator::GreaterEquals, + RangeOperator::GreaterEquals, Version::from_str("1.2").unwrap() )) ); assert_eq!( Constraint::from_str("<=1.2.*"), Ok(Constraint::Comparison( - VersionOperator::LessEquals, + RangeOperator::LessEquals, Version::from_str("1.2").unwrap() )) ); assert_eq!( Constraint::from_str("<1.2.*"), Ok(Constraint::Comparison( - VersionOperator::Less, + RangeOperator::Less, Version::from_str("1.2").unwrap() )) ); @@ -210,8 +217,8 @@ mod test { fn test_starts_with() { assert_eq!( Constraint::from_str("1.2.*"), - Ok(Constraint::Comparison( - VersionOperator::StartsWith, + Ok(Constraint::StrictComparison( + StrictRangeOperator::StartsWith, Version::from_str("1.2").unwrap() )) ); @@ -225,8 +232,8 @@ mod test { fn test_exact() { assert_eq!( Constraint::from_str("1.2.3"), - Ok(Constraint::Comparison( - VersionOperator::Equals, + Ok(Constraint::Exact( + EqualityOperator::Equals, Version::from_str("1.2.3").unwrap() )) ); diff --git a/crates/rattler_conda_types/src/version_spec/mod.rs b/crates/rattler_conda_types/src/version_spec/mod.rs index 7ea7a2530..246f4cf6b 100644 --- a/crates/rattler_conda_types/src/version_spec/mod.rs +++ b/crates/rattler_conda_types/src/version_spec/mod.rs @@ -15,43 +15,82 @@ use std::str::FromStr; use thiserror::Error; use version_tree::VersionTree; +use crate::version::StrictVersion; pub(crate) use constraint::is_start_of_version_constraint; pub(crate) use parse::ParseConstraintError; /// An operator to compare two versions. #[allow(missing_docs)] #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd, Serialize)] -pub enum VersionOperator { - Equals, - NotEquals, +pub enum RangeOperator { Greater, GreaterEquals, Less, LessEquals, +} + +impl RangeOperator { + /// Returns the complement of the current operator. + pub fn complement(self) -> Self { + match self { + RangeOperator::Greater => RangeOperator::LessEquals, + RangeOperator::GreaterEquals => RangeOperator::Less, + RangeOperator::Less => RangeOperator::GreaterEquals, + RangeOperator::LessEquals => RangeOperator::Greater, + } + } +} + +#[allow(missing_docs)] +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd, Serialize)] +pub enum StrictRangeOperator { StartsWith, NotStartsWith, Compatible, NotCompatible, } -impl VersionOperator { +impl StrictRangeOperator { + /// Returns the complement of the current operator. + pub fn complement(self) -> Self { + match self { + StrictRangeOperator::StartsWith => StrictRangeOperator::NotStartsWith, + StrictRangeOperator::NotStartsWith => StrictRangeOperator::StartsWith, + StrictRangeOperator::Compatible => StrictRangeOperator::NotCompatible, + StrictRangeOperator::NotCompatible => StrictRangeOperator::Compatible, + } + } +} + +/// An operator set a version equal to another +#[allow(missing_docs)] +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd, Serialize)] +pub enum EqualityOperator { + Equals, + NotEquals, +} + +impl EqualityOperator { /// Returns the complement of the current operator. pub fn complement(self) -> Self { match self { - VersionOperator::Equals => VersionOperator::NotEquals, - VersionOperator::NotEquals => VersionOperator::Equals, - VersionOperator::Greater => VersionOperator::LessEquals, - VersionOperator::GreaterEquals => VersionOperator::Less, - VersionOperator::Less => VersionOperator::GreaterEquals, - VersionOperator::LessEquals => VersionOperator::Greater, - VersionOperator::StartsWith => VersionOperator::NotStartsWith, - VersionOperator::NotStartsWith => VersionOperator::StartsWith, - VersionOperator::Compatible => VersionOperator::NotCompatible, - VersionOperator::NotCompatible => VersionOperator::Compatible, + EqualityOperator::Equals => EqualityOperator::NotEquals, + EqualityOperator::NotEquals => EqualityOperator::Equals, } } } +/// Range and equality operators combined +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd, Serialize)] +pub enum VersionOperators { + /// Specifies a range of versions + Range(RangeOperator), + /// Specifies a range of versions using the strict operator + StrictRange(StrictRangeOperator), + /// Specifies an exact version + Exact(EqualityOperator), +} + /// Logical operator used two compare groups of version comparisions. E.g. `>=3.4,<4.0` or /// `>=3.4|<4.0`, #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd, Serialize)] @@ -81,8 +120,12 @@ pub enum VersionSpec { None, /// Any version Any, - /// A specific version - Operator(VersionOperator, Version), + /// A version range + Range(RangeOperator, Version), + /// A version range using the strict operator + StrictRange(StrictRangeOperator, StrictVersion), + /// A exact version + Exact(EqualityOperator, Version), /// A group of version specifications Group(LogicalOperator, Vec), } @@ -114,7 +157,11 @@ impl FromStr for VersionSpec { .map_err(ParseVersionSpecError::InvalidConstraint)?; Ok(match constraint { Constraint::Any => VersionSpec::Any, - Constraint::Comparison(op, ver) => VersionSpec::Operator(op, ver), + Constraint::Comparison(op, ver) => VersionSpec::Range(op, ver), + Constraint::StrictComparison(op, ver) => { + VersionSpec::StrictRange(op, StrictVersion(ver)) + } + Constraint::Exact(e, ver) => VersionSpec::Exact(e, ver), }) } VersionTree::Group(op, groups) => Ok(VersionSpec::Group( @@ -131,19 +178,33 @@ impl FromStr for VersionSpec { } } -impl Display for VersionOperator { +impl Display for RangeOperator { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + RangeOperator::Greater => write!(f, ">"), + RangeOperator::GreaterEquals => write!(f, ">="), + RangeOperator::Less => write!(f, "<"), + RangeOperator::LessEquals => write!(f, "<="), + } + } +} + +impl Display for StrictRangeOperator { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { - VersionOperator::Equals => write!(f, "=="), - VersionOperator::NotEquals => write!(f, "!="), - VersionOperator::Greater => write!(f, ">"), - VersionOperator::GreaterEquals => write!(f, ">="), - VersionOperator::Less => write!(f, "<"), - VersionOperator::LessEquals => write!(f, "<="), - VersionOperator::StartsWith => write!(f, "="), - VersionOperator::NotStartsWith => write!(f, "!=startswith"), - VersionOperator::Compatible => write!(f, "~="), - VersionOperator::NotCompatible => write!(f, "!~="), + StrictRangeOperator::StartsWith => write!(f, "="), + StrictRangeOperator::NotStartsWith => write!(f, "!=startswith"), + StrictRangeOperator::Compatible => write!(f, "~="), + StrictRangeOperator::NotCompatible => write!(f, "!~="), + } + } +} + +impl Display for EqualityOperator { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + Self::Equals => write!(f, "=="), + Self::NotEquals => write!(f, "!="), } } } @@ -162,11 +223,17 @@ impl Display for VersionSpec { fn write(spec: &VersionSpec, f: &mut Formatter<'_>, part_of_or: bool) -> std::fmt::Result { match spec { VersionSpec::Any => write!(f, "*"), - VersionSpec::Operator(op, version) => match op { - VersionOperator::StartsWith => write!(f, "{}.*", version), - VersionOperator::NotStartsWith => write!(f, "!={}.*", version), + VersionSpec::StrictRange(op, version) => match op { + StrictRangeOperator::StartsWith => write!(f, "{}.*", version), + StrictRangeOperator::NotStartsWith => write!(f, "!={}.*", version), op => write!(f, "{}{}", op, version), }, + VersionSpec::Range(op, version) => { + write!(f, "{}{}", op, version) + } + VersionSpec::Exact(op, version) => { + write!(f, "{}{}", op, version) + } VersionSpec::Group(op, group) => { let requires_parenthesis = *op == LogicalOperator::And && part_of_or; if requires_parenthesis { @@ -206,21 +273,23 @@ impl VersionSpec { match self { VersionSpec::None => false, VersionSpec::Any => true, - VersionSpec::Operator(VersionOperator::Equals, limit) => limit == version, - VersionSpec::Operator(VersionOperator::NotEquals, limit) => limit != version, - VersionSpec::Operator(VersionOperator::Greater, limit) => version > limit, - VersionSpec::Operator(VersionOperator::GreaterEquals, limit) => version >= limit, - VersionSpec::Operator(VersionOperator::Less, limit) => version < limit, - VersionSpec::Operator(VersionOperator::LessEquals, limit) => version <= limit, - VersionSpec::Operator(VersionOperator::StartsWith, limit) => version.starts_with(limit), - VersionSpec::Operator(VersionOperator::NotStartsWith, limit) => { - !version.starts_with(limit) + VersionSpec::Exact(EqualityOperator::Equals, limit) => limit == version, + VersionSpec::Exact(EqualityOperator::NotEquals, limit) => limit != version, + VersionSpec::Range(RangeOperator::Greater, limit) => version > limit, + VersionSpec::Range(RangeOperator::GreaterEquals, limit) => version >= limit, + VersionSpec::Range(RangeOperator::Less, limit) => version < limit, + VersionSpec::Range(RangeOperator::LessEquals, limit) => version <= limit, + VersionSpec::StrictRange(StrictRangeOperator::StartsWith, limit) => { + version.starts_with(&limit.0) + } + VersionSpec::StrictRange(StrictRangeOperator::NotStartsWith, limit) => { + !version.starts_with(&limit.0) } - VersionSpec::Operator(VersionOperator::Compatible, limit) => { - version.compatible_with(limit) + VersionSpec::StrictRange(StrictRangeOperator::Compatible, limit) => { + version.compatible_with(&limit.0) } - VersionSpec::Operator(VersionOperator::NotCompatible, limit) => { - !version.compatible_with(limit) + VersionSpec::StrictRange(StrictRangeOperator::NotCompatible, limit) => { + !version.compatible_with(&limit.0) } VersionSpec::Group(LogicalOperator::And, group) => { group.iter().all(|spec| spec.matches(version)) @@ -234,7 +303,7 @@ impl VersionSpec { #[cfg(test)] mod tests { - use crate::version_spec::{LogicalOperator, VersionOperator}; + use crate::version_spec::{EqualityOperator, LogicalOperator, RangeOperator}; use crate::{Version, VersionSpec}; use std::str::FromStr; @@ -242,15 +311,15 @@ mod tests { fn test_simple() { assert_eq!( VersionSpec::from_str("1.2.3"), - Ok(VersionSpec::Operator( - VersionOperator::Equals, + Ok(VersionSpec::Exact( + EqualityOperator::Equals, Version::from_str("1.2.3").unwrap() )) ); assert_eq!( VersionSpec::from_str(">=1.2.3"), - Ok(VersionSpec::Operator( - VersionOperator::GreaterEquals, + Ok(VersionSpec::Range( + RangeOperator::GreaterEquals, Version::from_str("1.2.3").unwrap() )) ); @@ -263,14 +332,11 @@ mod tests { Ok(VersionSpec::Group( LogicalOperator::And, vec![ - VersionSpec::Operator( - VersionOperator::GreaterEquals, + VersionSpec::Range( + RangeOperator::GreaterEquals, Version::from_str("1.2.3").unwrap() ), - VersionSpec::Operator( - VersionOperator::Less, - Version::from_str("2.0.0").unwrap() - ), + VersionSpec::Range(RangeOperator::Less, Version::from_str("2.0.0").unwrap()), ] )) ); @@ -279,14 +345,11 @@ mod tests { Ok(VersionSpec::Group( LogicalOperator::Or, vec![ - VersionSpec::Operator( - VersionOperator::GreaterEquals, + VersionSpec::Range( + RangeOperator::GreaterEquals, Version::from_str("1.2.3").unwrap() ), - VersionSpec::Operator( - VersionOperator::Less, - Version::from_str("1.0.0").unwrap() - ), + VersionSpec::Range(RangeOperator::Less, Version::from_str("1.0.0").unwrap()), ] )) ); @@ -295,14 +358,11 @@ mod tests { Ok(VersionSpec::Group( LogicalOperator::Or, vec![ - VersionSpec::Operator( - VersionOperator::GreaterEquals, + VersionSpec::Range( + RangeOperator::GreaterEquals, Version::from_str("1.2.3").unwrap() ), - VersionSpec::Operator( - VersionOperator::Less, - Version::from_str("1.0.0").unwrap() - ), + VersionSpec::Range(RangeOperator::Less, Version::from_str("1.0.0").unwrap()), ] )) ); diff --git a/crates/rattler_conda_types/src/version_spec/parse.rs b/crates/rattler_conda_types/src/version_spec/parse.rs index a3a68dc76..728f5a9a4 100644 --- a/crates/rattler_conda_types/src/version_spec/parse.rs +++ b/crates/rattler_conda_types/src/version_spec/parse.rs @@ -1,6 +1,6 @@ use crate::version::parse::version_parser; use crate::version_spec::constraint::Constraint; -use crate::version_spec::VersionOperator; +use crate::version_spec::{EqualityOperator, RangeOperator, StrictRangeOperator, VersionOperators}; use crate::{ParseVersionError, ParseVersionErrorKind}; use nom::{ branch::alt, @@ -22,7 +22,7 @@ enum ParseVersionOperatorError<'i> { } /// Parses a version operator, returns an error if the operator is not recognized or not found. -fn operator_parser(input: &str) -> IResult<&str, VersionOperator, ParseVersionOperatorError> { +fn operator_parser(input: &str) -> IResult<&str, VersionOperators, ParseVersionOperatorError> { // Take anything that looks like an operator. let (rest, operator_str) = take_while1(|c| "=!<>~".contains(c))(input).map_err( |_: nom::Err>| { @@ -31,14 +31,14 @@ fn operator_parser(input: &str) -> IResult<&str, VersionOperator, ParseVersionOp )?; let op = match operator_str { - "==" => VersionOperator::Equals, - "!=" => VersionOperator::NotEquals, - "<=" => VersionOperator::LessEquals, - ">=" => VersionOperator::GreaterEquals, - "<" => VersionOperator::Less, - ">" => VersionOperator::Greater, - "=" => VersionOperator::StartsWith, - "~=" => VersionOperator::Compatible, + "==" => VersionOperators::Exact(EqualityOperator::Equals), + "!=" => VersionOperators::Exact(EqualityOperator::NotEquals), + "<=" => VersionOperators::Range(RangeOperator::LessEquals), + ">=" => VersionOperators::Range(RangeOperator::GreaterEquals), + "<" => VersionOperators::Range(RangeOperator::Less), + ">" => VersionOperators::Range(RangeOperator::Greater), + "=" => VersionOperators::StrictRange(StrictRangeOperator::StartsWith), + "~=" => VersionOperators::StrictRange(StrictRangeOperator::Compatible), _ => { return Err(nom::Err::Failure( ParseVersionOperatorError::InvalidOperator(operator_str), @@ -52,7 +52,7 @@ fn operator_parser(input: &str) -> IResult<&str, VersionOperator, ParseVersionOp #[derive(Debug, Clone, Error, Eq, PartialEq)] pub enum ParseConstraintError { #[error("'.' is incompatible with '{0}' operator'")] - GlobVersionIncompatibleWithOperator(VersionOperator), + GlobVersionIncompatibleWithOperator(RangeOperator), #[error("regex constraints are not supported")] RegexConstraintsNotSupported, #[error("unterminated unsupported regular expression")] @@ -142,18 +142,26 @@ fn logical_constraint_parser(input: &str) -> IResult<&str, Constraint, ParseCons let op = match (version_rest, op) { // The version was successfully parsed ("", Some(op)) => op, - ("", None) => VersionOperator::Equals, + ("", None) => VersionOperators::Exact(EqualityOperator::Equals), // The version ends in a wildcard pattern - ("*" | ".*", Some(VersionOperator::StartsWith)) => VersionOperator::StartsWith, - ("*" | ".*", Some(VersionOperator::GreaterEquals)) => VersionOperator::GreaterEquals, - ("*" | ".*", Some(VersionOperator::Greater)) => VersionOperator::GreaterEquals, - ("*" | ".*", Some(VersionOperator::NotEquals)) => VersionOperator::NotStartsWith, + ("*" | ".*", Some(VersionOperators::StrictRange(StrictRangeOperator::StartsWith))) => { + VersionOperators::StrictRange(StrictRangeOperator::StartsWith) + } + ("*" | ".*", Some(VersionOperators::Range(RangeOperator::GreaterEquals))) => { + VersionOperators::Range(RangeOperator::GreaterEquals) + } + ("*" | ".*", Some(VersionOperators::Range(RangeOperator::Greater))) => { + VersionOperators::Range(RangeOperator::GreaterEquals) + } + ("*" | ".*", Some(VersionOperators::Exact(EqualityOperator::NotEquals))) => { + VersionOperators::StrictRange(StrictRangeOperator::NotStartsWith) + } (glob @ "*" | glob @ ".*", Some(op)) => { tracing::warn!("Using {glob} with relational operator is superfluous and deprecated and will be removed in a future version of conda."); op } - ("*" | ".*", None) => VersionOperator::StartsWith, + ("*" | ".*", None) => VersionOperators::StrictRange(StrictRangeOperator::StartsWith), // The version string kinda looks like a regular expression. (version_remainder, _) if version_str.contains('*') || version_remainder.ends_with('$') => { @@ -173,7 +181,11 @@ fn logical_constraint_parser(input: &str) -> IResult<&str, Constraint, ParseCons } }; - Ok((rest, Constraint::Comparison(op, version))) + match op { + VersionOperators::Range(r) => Ok((rest, Constraint::Comparison(r, version))), + VersionOperators::Exact(e) => Ok((rest, Constraint::Exact(e, version))), + VersionOperators::StrictRange(s) => Ok((rest, Constraint::StrictComparison(s, version))), + } } /// Parses a version constraint. @@ -195,32 +207,41 @@ mod test { fn test_operator_parser() { assert_eq!( operator_parser(">3.1"), - Ok(("3.1", VersionOperator::Greater)) + Ok(("3.1", VersionOperators::Range(RangeOperator::Greater))) ); assert_eq!( operator_parser(">=3.1"), - Ok(("3.1", VersionOperator::GreaterEquals)) + Ok(("3.1", VersionOperators::Range(RangeOperator::GreaterEquals))) + ); + assert_eq!( + operator_parser("<3.1"), + Ok(("3.1", VersionOperators::Range(RangeOperator::Less))) ); - assert_eq!(operator_parser("<3.1"), Ok(("3.1", VersionOperator::Less))); assert_eq!( operator_parser("<=3.1"), - Ok(("3.1", VersionOperator::LessEquals)) + Ok(("3.1", VersionOperators::Range(RangeOperator::LessEquals))) ); assert_eq!( operator_parser("==3.1"), - Ok(("3.1", VersionOperator::Equals)) + Ok(("3.1", VersionOperators::Exact(EqualityOperator::Equals))) ); assert_eq!( operator_parser("!=3.1"), - Ok(("3.1", VersionOperator::NotEquals)) + Ok(("3.1", VersionOperators::Exact(EqualityOperator::NotEquals))) ); assert_eq!( operator_parser("=3.1"), - Ok(("3.1", VersionOperator::StartsWith)) + Ok(( + "3.1", + VersionOperators::StrictRange(StrictRangeOperator::StartsWith) + )) ); assert_eq!( operator_parser("~=3.1"), - Ok(("3.1", VersionOperator::Compatible)) + Ok(( + "3.1", + VersionOperators::StrictRange(StrictRangeOperator::Compatible) + )) ); assert_eq!( @@ -265,7 +286,7 @@ mod test { logical_constraint_parser("3.1"), Ok(( "", - Constraint::Comparison(VersionOperator::Equals, Version::from_str("3.1").unwrap()) + Constraint::Exact(EqualityOperator::Equals, Version::from_str("3.1").unwrap()) )) ); @@ -273,7 +294,7 @@ mod test { logical_constraint_parser(">3.1"), Ok(( "", - Constraint::Comparison(VersionOperator::Greater, Version::from_str("3.1").unwrap()) + Constraint::Comparison(RangeOperator::Greater, Version::from_str("3.1").unwrap()) )) ); @@ -281,8 +302,8 @@ mod test { logical_constraint_parser("3.1*"), Ok(( "", - Constraint::Comparison( - VersionOperator::StartsWith, + Constraint::StrictComparison( + StrictRangeOperator::StartsWith, Version::from_str("3.1").unwrap() ) )) @@ -292,8 +313,8 @@ mod test { logical_constraint_parser("3.1.*"), Ok(( "", - Constraint::Comparison( - VersionOperator::StartsWith, + Constraint::StrictComparison( + StrictRangeOperator::StartsWith, Version::from_str("3.1").unwrap() ) )) @@ -303,8 +324,8 @@ mod test { logical_constraint_parser("~=3.1"), Ok(( "", - Constraint::Comparison( - VersionOperator::Compatible, + Constraint::StrictComparison( + StrictRangeOperator::Compatible, Version::from_str("3.1").unwrap() ) )) @@ -315,7 +336,7 @@ mod test { Ok(( "", Constraint::Comparison( - VersionOperator::GreaterEquals, + RangeOperator::GreaterEquals, Version::from_str("3.1").unwrap() ) )) diff --git a/crates/rattler_conda_types/src/version_spec/version_tree.rs b/crates/rattler_conda_types/src/version_spec/version_tree.rs index e29338c70..c7bd933ba 100644 --- a/crates/rattler_conda_types/src/version_spec/version_tree.rs +++ b/crates/rattler_conda_types/src/version_spec/version_tree.rs @@ -1,4 +1,6 @@ -use crate::version_spec::{LogicalOperator, VersionOperator}; +use crate::version_spec::{ + EqualityOperator, LogicalOperator, RangeOperator, StrictRangeOperator, VersionOperators, +}; use nom::{ branch::alt, bytes::complete::{tag, take_while}, @@ -27,16 +29,31 @@ pub enum ParseVersionTreeError { /// A parser that parses version operators. fn parse_operator<'a, E: ParseError<&'a str>>( input: &'a str, -) -> Result<(&'a str, VersionOperator), nom::Err> { +) -> Result<(&'a str, VersionOperators), nom::Err> { alt(( - value(VersionOperator::Equals, tag("==")), - value(VersionOperator::StartsWith, tag("=")), - value(VersionOperator::NotEquals, tag("!=")), - value(VersionOperator::GreaterEquals, tag(">=")), - value(VersionOperator::Greater, tag(">")), - value(VersionOperator::LessEquals, tag("<=")), - value(VersionOperator::Less, tag("<")), - value(VersionOperator::Compatible, tag("~=")), + value(VersionOperators::Exact(EqualityOperator::Equals), tag("==")), + value( + VersionOperators::Exact(EqualityOperator::NotEquals), + tag("!="), + ), + value( + VersionOperators::StrictRange(StrictRangeOperator::StartsWith), + tag("="), + ), + value( + VersionOperators::Range(RangeOperator::GreaterEquals), + tag(">="), + ), + value(VersionOperators::Range(RangeOperator::Greater), tag(">")), + value( + VersionOperators::Range(RangeOperator::LessEquals), + tag("<="), + ), + value(VersionOperators::Range(RangeOperator::Less), tag("<")), + value( + VersionOperators::StrictRange(StrictRangeOperator::Compatible), + tag("~="), + ), ))(input) } @@ -186,7 +203,9 @@ impl<'a> TryFrom<&'a str> for VersionTree<'a> { mod tests { use super::{parse_operator, recognize_version, LogicalOperator, VersionTree}; use crate::version_spec::version_tree::{parse_version_epoch, recognize_constraint}; - use crate::version_spec::VersionOperator; + use crate::version_spec::{ + EqualityOperator, RangeOperator, StrictRangeOperator, VersionOperators, + }; use std::convert::TryFrom; #[test] @@ -245,32 +264,41 @@ mod tests { assert_eq!( parse_operator::("=="), - Ok(("", VersionOperator::Equals)) + Ok(("", VersionOperators::Exact(EqualityOperator::Equals))) ); assert_eq!( parse_operator::("!="), - Ok(("", VersionOperator::NotEquals)) + Ok(("", VersionOperators::Exact(EqualityOperator::NotEquals))) ); assert_eq!( parse_operator::(">"), - Ok(("", VersionOperator::Greater)) + Ok(("", VersionOperators::Range(RangeOperator::Greater))) ); assert_eq!( parse_operator::(">="), - Ok(("", VersionOperator::GreaterEquals)) + Ok(("", VersionOperators::Range(RangeOperator::GreaterEquals))) + ); + assert_eq!( + parse_operator::("<"), + Ok(("", VersionOperators::Range(RangeOperator::Less))) ); - assert_eq!(parse_operator::("<"), Ok(("", VersionOperator::Less))); assert_eq!( parse_operator::("<="), - Ok(("", VersionOperator::LessEquals)) + Ok(("", VersionOperators::Range(RangeOperator::LessEquals))) ); assert_eq!( parse_operator::("="), - Ok(("", VersionOperator::StartsWith)) + Ok(( + "", + VersionOperators::StrictRange(StrictRangeOperator::StartsWith) + )) ); assert_eq!( parse_operator::("~="), - Ok(("", VersionOperator::Compatible)) + Ok(( + "", + VersionOperators::StrictRange(StrictRangeOperator::Compatible) + )) ); // Anything else is an error @@ -280,7 +308,7 @@ mod tests { // Only the operator is parsed assert_eq!( parse_operator::(">=3.8"), - Ok(("3.8", VersionOperator::GreaterEquals)) + Ok(("3.8", VersionOperators::Range(RangeOperator::GreaterEquals))) ); } diff --git a/crates/rattler_solve/tests/backends.rs b/crates/rattler_solve/tests/backends.rs index e9875e0cd..67f129e48 100644 --- a/crates/rattler_solve/tests/backends.rs +++ b/crates/rattler_solve/tests/backends.rs @@ -425,7 +425,7 @@ mod libsolv_c { locked_packages: Vec::new(), virtual_packages: Vec::new(), available_packages: [libsolv_repodata], - specs: specs.clone(), + specs: specs, pinned_packages: Vec::new(), }) .unwrap(); @@ -488,10 +488,10 @@ fn solve( .collect(); let task = SolverTask { - locked_packages: installed_packages.clone(), - virtual_packages: virtual_packages.clone(), + locked_packages: installed_packages, + virtual_packages: virtual_packages, available_packages: [&repo_data], - specs: specs.clone(), + specs: specs, pinned_packages: Vec::new(), };