Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for sha256 and md5 field in matchspec #241

Merged
merged 8 commits into from
Jul 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 {
baszalmstra marked this conversation as resolved.
Show resolved Hide resolved
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