Skip to content

Commit

Permalink
Add support for sha256 and md5 field in matchspec (#241)
Browse files Browse the repository at this point in the history
  • Loading branch information
0xbe7a authored Jul 2, 2023
1 parent 1dc0beb commit f3eddf0
Show file tree
Hide file tree
Showing 4 changed files with 186 additions and 16 deletions.
154 changes: 140 additions & 14 deletions crates/rattler_conda_types/src/match_spec/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use crate::{PackageRecord, VersionSpec};
use rattler_digest::{serde::SerializableHash, Md5Hash, Sha256Hash};
use serde::Serialize;
use serde_with::skip_serializing_none;
use serde_with::{serde_as, skip_serializing_none};
use std::fmt::{Debug, Display, Formatter};

pub mod matcher;
Expand Down Expand Up @@ -109,6 +110,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)]
pub struct MatchSpec {
/// The name of the package
Expand All @@ -127,6 +129,12 @@ pub struct MatchSpec {
pub subdir: Option<String>,
/// The namespace of the package (currently not used)
pub namespace: Option<String>,
/// The md5 hash of the package
#[serde_as(as = "Option<SerializableHash::<rattler_digest::Md5>>")]
pub md5: Option<Md5Hash>,
/// The sha256 hash of the package
#[serde_as(as = "Option<SerializableHash::<rattler_digest::Sha256>>")]
pub sha256: Option<Sha256Hash>,
}

impl Display for MatchSpec {
Expand All @@ -140,25 +148,37 @@ impl Display for MatchSpec {
write!(f, "/{}", subdir)?;
}

match &self.name {
Some(name) => write!(f, "{name}")?,
None => write!(f, "*")?,
}

if let Some(namespace) = &self.namespace {
write!(f, ":{}:", namespace)?;
} else if self.channel.is_some() || self.subdir.is_some() {
write!(f, "::")?;
}

match &self.name {
Some(name) => write!(f, "{name}")?,
None => write!(f, "*")?,
if let Some(version) = &self.version {
write!(f, " {}", version)?;
}

match &self.version {
Some(version) => write!(f, " {version}")?,
None => (),
if let Some(build) = &self.build {
write!(f, " {}", build)?;
}

let mut keys = Vec::new();

if let Some(md5) = &self.md5 {
keys.push(format!("md5={md5:x}"));
}

if let Some(sha256) = &self.sha256 {
keys.push(format!("sha256={sha256:x}"));
}

match &self.build {
Some(build) => write!(f, " {build}")?,
None => (),
if !keys.is_empty() {
write!(f, "[{}]", keys.join(", "))?;
}

Ok(())
Expand Down Expand Up @@ -186,12 +206,25 @@ impl MatchSpec {
}
}

if let Some(md5_spec) = self.md5.as_ref() {
if Some(md5_spec) != record.md5.as_ref() {
return false;
}
}

if let Some(sha256_spec) = self.sha256.as_ref() {
if Some(sha256_spec) != record.sha256.as_ref() {
return false;
}
}

true
}
}

/// Similar to a [`MatchSpec`] but does not include the package name. This is useful in places
/// where the package name is already known (e.g. `foo = "3.4.1 *cuda"`)
#[serde_as]
#[skip_serializing_none]
#[derive(Debug, Default, Clone, Serialize, Eq, PartialEq)]
pub struct NamelessMatchSpec {
Expand All @@ -209,6 +242,12 @@ pub struct NamelessMatchSpec {
pub subdir: Option<String>,
/// The namespace of the package (currently not used)
pub namespace: Option<String>,
/// The md5 hash of the package
#[serde_as(as = "Option<SerializableHash::<rattler_digest::Md5>>")]
pub md5: Option<Md5Hash>,
/// The sha256 hash of the package
#[serde_as(as = "Option<SerializableHash::<rattler_digest::Sha256>>")]
pub sha256: Option<Sha256Hash>,
}

impl NamelessMatchSpec {
Expand All @@ -226,6 +265,18 @@ impl NamelessMatchSpec {
}
}

if let Some(md5_spec) = self.md5.as_ref() {
if Some(md5_spec) != record.md5.as_ref() {
return false;
}
}

if let Some(sha256_spec) = self.sha256.as_ref() {
if Some(sha256_spec) != record.sha256.as_ref() {
return false;
}
}

true
}
}
Expand All @@ -237,12 +288,23 @@ impl Display for NamelessMatchSpec {
None => write!(f, "*")?,
}

match &self.build {
Some(build) => write!(f, " {build}")?,
None => (),
if let Some(build) = &self.build {
write!(f, " {}", build)?;
}

let mut keys = Vec::new();

if let Some(md5) = &self.md5 {
keys.push(format!("md5={md5:x}"));
}

if let Some(sha256) = &self.sha256 {
keys.push(format!("sha256={sha256:x}"));
}

// TODO: Add any additional properties as bracket arguments (e.g. `[channel=..]`)
if !keys.is_empty() {
write!(f, "[{}]", keys.join(", "))?;
}

Ok(())
}
Expand All @@ -258,6 +320,8 @@ impl From<MatchSpec> for NamelessMatchSpec {
channel: spec.channel,
subdir: spec.subdir,
namespace: spec.namespace,
md5: spec.md5,
sha256: spec.sha256,
}
}
}
Expand All @@ -274,6 +338,68 @@ impl MatchSpec {
channel: spec.channel,
subdir: spec.subdir,
namespace: spec.namespace,
md5: spec.md5,
sha256: spec.sha256,
}
}
}

#[cfg(test)]
mod tests {
use std::str::FromStr;

use rattler_digest::{parse_digest_from_hex, Md5, Sha256};

use crate::{MatchSpec, NamelessMatchSpec, PackageRecord, Version};

#[test]
fn test_matchspec_format_eq() {
let spec = MatchSpec::from_str("mamba[version==1.0, sha256=aaac4bc9c6916ecc0e33137431645b029ade22190c7144eead61446dcbcc6f97, md5=dede6252c964db3f3e41c7d30d07f6bf]").unwrap();
let spec_as_string = spec.to_string();
let rebuild_spec = MatchSpec::from_str(&spec_as_string).unwrap();

assert_eq!(spec, rebuild_spec)
}

#[test]
fn test_nameless_matchspec_format_eq() {
let spec = NamelessMatchSpec::from_str("*[version==1.0, sha256=aaac4bc9c6916ecc0e33137431645b029ade22190c7144eead61446dcbcc6f97, md5=dede6252c964db3f3e41c7d30d07f6bf]").unwrap();
let spec_as_string = spec.to_string();
let rebuild_spec = NamelessMatchSpec::from_str(&spec_as_string).unwrap();

assert_eq!(spec, rebuild_spec)
}

#[test]
fn test_digest_match() {
let record = PackageRecord {
name: "mamba".to_string(),
version: Version::from_str("1.0").unwrap(),
sha256: parse_digest_from_hex::<Sha256>(
"f44c4bc9c6916ecc0e33137431645b029ade22190c7144eead61446dcbcc6f97",
),
md5: parse_digest_from_hex::<Md5>("dede6252c964db3f3e41c7d30d07f6bf"),
..PackageRecord::default()
};

let spec = MatchSpec::from_str("mamba[version==1.0, sha256=aaac4bc9c6916ecc0e33137431645b029ade22190c7144eead61446dcbcc6f97]").unwrap();
assert!(!spec.matches(&record));

let spec = MatchSpec::from_str("mamba[version==1.0, sha256=f44c4bc9c6916ecc0e33137431645b029ade22190c7144eead61446dcbcc6f97]").unwrap();
assert!(spec.matches(&record));

let spec = MatchSpec::from_str("mamba[version==1.0, md5=aaaa6252c964db3f3e41c7d30d07f6bf]")
.unwrap();
assert!(!spec.matches(&record));

let spec = MatchSpec::from_str("mamba[version==1.0, md5=dede6252c964db3f3e41c7d30d07f6bf]")
.unwrap();
assert!(spec.matches(&record));

let spec = MatchSpec::from_str("mamba[version==1.0, md5=dede6252c964db3f3e41c7d30d07f6bf, sha256=f44c4bc9c6916ecc0e33137431645b029ade22190c7144eead61446dcbcc6f97]").unwrap();
assert!(spec.matches(&record));

let spec = MatchSpec::from_str("mamba[version==1.0, md5=dede6252c964db3f3e41c7d30d07f6bf, sha256=aaac4bc9c6916ecc0e33137431645b029ade22190c7144eead61446dcbcc6f97]").unwrap();
assert!(!spec.matches(&record));
}
}
44 changes: 44 additions & 0 deletions crates/rattler_conda_types/src/match_spec/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use nom::error::{context, ContextError, ParseError};
use nom::multi::{separated_list0, separated_list1};
use nom::sequence::{delimited, preceded, separated_pair, terminated};
use nom::{Finish, IResult};
use rattler_digest::{parse_digest_from_hex, Md5, Sha256};
use smallvec::SmallVec;
use std::borrow::Cow;
use std::num::ParseIntError;
Expand Down Expand Up @@ -54,6 +55,9 @@ pub enum ParseMatchSpecError {

#[error("invalid build number: {0}")]
InvalidBuildNumber(#[from] ParseIntError),

#[error("Unable to parse hash digest from hex")]
InvalidHashDigest,
}

impl FromStr for MatchSpec {
Expand Down Expand Up @@ -183,6 +187,18 @@ fn parse_bracket_vec_into_components(
"version" => match_spec.version = Some(VersionSpec::from_str(value)?),
"build" => match_spec.build = Some(StringMatcher::from_str(value)?),
"build_number" => match_spec.build_number = Some(value.parse()?),
"sha256" => {
match_spec.sha256 = Some(
parse_digest_from_hex::<Sha256>(value)
.ok_or(ParseMatchSpecError::InvalidHashDigest)?,
)
}
"md5" => {
match_spec.md5 = Some(
parse_digest_from_hex::<Md5>(value)
.ok_or(ParseMatchSpecError::InvalidHashDigest)?,
)
}
"fn" => match_spec.file_name = Some(value.to_string()),
_ => Err(ParseMatchSpecError::InvalidBracketKey(key.to_owned()))?,
}
Expand Down Expand Up @@ -425,6 +441,7 @@ fn parse(input: &str) -> Result<MatchSpec, ParseMatchSpecError> {

#[cfg(test)]
mod tests {
use rattler_digest::{parse_digest_from_hex, Md5, Sha256};
use serde::Serialize;
use std::collections::BTreeMap;
use std::str::FromStr;
Expand Down Expand Up @@ -541,6 +558,33 @@ mod tests {
assert_eq!(spec.channel, Some("conda-forge".to_string()));
}

#[test]
fn test_hash_spec() {
let spec = MatchSpec::from_str("conda-forge::foo[md5=1234567890]");
assert_eq!(spec, Err(ParseMatchSpecError::InvalidHashDigest));

let spec = MatchSpec::from_str("conda-forge::foo[sha256=1234567890]");
assert_eq!(spec, Err(ParseMatchSpecError::InvalidHashDigest));

let spec = MatchSpec::from_str("conda-forge::foo[sha256=315f5bdb76d078c43b8ac0064e4a0164612b1fce77c869345bfc94c75894edd3]").unwrap();
assert_eq!(
spec.sha256,
Some(
parse_digest_from_hex::<Sha256>(
"315f5bdb76d078c43b8ac0064e4a0164612b1fce77c869345bfc94c75894edd3"
)
.unwrap()
)
);

let spec =
MatchSpec::from_str("conda-forge::foo[md5=8b1a9953c4611296a827abf8c47804d7]").unwrap();
assert_eq!(
spec.md5,
Some(parse_digest_from_hex::<Md5>("8b1a9953c4611296a827abf8c47804d7").unwrap())
);
}

#[test]
fn test_parse_bracket_list() {
assert_eq!(
Expand Down
2 changes: 1 addition & 1 deletion crates/rattler_conda_types/src/repo_data/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ pub struct ChannelInfo {
#[serde_as]
#[skip_serializing_none]
#[sorted]
#[derive(Debug, Deserialize, Serialize, Eq, PartialEq, Ord, PartialOrd, Clone, Hash)]
#[derive(Debug, Deserialize, Serialize, Eq, PartialEq, Ord, PartialOrd, Clone, Hash, Default)]
pub struct PackageRecord {
/// Optionally the architecture the package supports
pub arch: Option<String>,
Expand Down
2 changes: 1 addition & 1 deletion crates/rattler_conda_types/src/version/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ const LOCAL_VERSION_OFFSET: u8 = 1;
/// this problem by appending an underscore to plain version numbers:
///
/// 1.0.1_ < 1.0.1a => True # ensure correct ordering for openssl
#[derive(Clone, Eq, Deserialize)]
#[derive(Clone, Eq, Deserialize, Default)]
pub struct Version {
/// A normed copy of the original version string trimmed and converted to lower case.
/// Also dashes are replaced with underscores if the version string does not contain
Expand Down

0 comments on commit f3eddf0

Please sign in to comment.