Skip to content

Commit

Permalink
feat: Match GenericVirtualPackage with MatchSpec (#1016)
Browse files Browse the repository at this point in the history
  • Loading branch information
ruben-arts authored Jan 9, 2025
1 parent 21b844b commit 899ab59
Show file tree
Hide file tree
Showing 5 changed files with 592 additions and 2 deletions.
79 changes: 78 additions & 1 deletion crates/rattler_conda_types/src/match_spec/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
use crate::{build_spec::BuildNumberSpec, PackageName, PackageRecord, RepoDataRecord, VersionSpec};
use crate::{
build_spec::BuildNumberSpec, GenericVirtualPackage, PackageName, PackageRecord, RepoDataRecord,
VersionSpec,
};
use rattler_digest::{serde::SerializableHash, Md5Hash, Sha256Hash};
use serde::{Deserialize, Deserializer, Serialize};
use serde_with::{serde_as, skip_serializing_none};
Expand Down Expand Up @@ -227,6 +230,15 @@ impl MatchSpec {
},
)
}

/// Returns whether the package is a virtual package.
/// This is determined by the package name starting with `__`.
/// Not having a package name is considered not virtual.
pub fn is_virtual(&self) -> bool {
self.name
.as_ref()
.map_or(false, |name| name.as_normalized().starts_with("__"))
}
}

// Enable constructing a match spec from a package name.
Expand Down Expand Up @@ -479,9 +491,34 @@ impl Matches<RepoDataRecord> for NamelessMatchSpec {
}
}

impl Matches<GenericVirtualPackage> for MatchSpec {
/// Match a [`MatchSpec`] against a [`GenericVirtualPackage`]
fn matches(&self, other: &GenericVirtualPackage) -> bool {
if let Some(name) = self.name.as_ref() {
if name != &other.name {
return false;
}
}

if let Some(spec) = self.version.as_ref() {
if !spec.matches(&other.version) {
return false;
}
}

if let Some(build_string) = self.build.as_ref() {
if !build_string.matches(&other.build_string) {
return false;
}
}
true
}
}

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

use rattler_digest::{parse_digest_from_hex, Md5, Sha256};
Expand Down Expand Up @@ -732,4 +769,44 @@ mod tests {
.format("\n")
.to_string());
}

#[rstest]
#[case("foo >=1.0 py37_0", true)]
#[case("foo >=1.0 py37*", true)]
#[case("foo 1.0.* py38*", false)]
#[case("foo * py37_1", false)]
#[case("foo ==1.0", true)]
#[case("foo >=2.0", false)]
#[case("foo >=1.0", true)]
#[case("foo", true)]
#[case("bar", false)]
fn test_match_generic_virtual_package(#[case] spec_str: &str, #[case] expected: bool) {
let virtual_package = crate::GenericVirtualPackage {
name: PackageName::new_unchecked("foo"),
version: Version::from_str("1.0").unwrap(),
build_string: String::from("py37_0"),
};

let spec = MatchSpec::from_str(spec_str, Strict).unwrap();
assert_eq!(spec.matches(&virtual_package), expected);
}

#[test]
fn test_is_virtual() {
let spec = MatchSpec::from_str("non_virtual_name", Strict).unwrap();
assert!(!spec.is_virtual());

let spec = MatchSpec::from_str("__virtual_name", Strict).unwrap();
assert!(spec.is_virtual());

let spec = MatchSpec::from_str("non_virtual_name >=12", Strict).unwrap();
assert!(!spec.is_virtual());

let spec = MatchSpec::from_str("__virtual_name >=12", Strict).unwrap();
assert!(spec.is_virtual());

let spec =
MatchSpec::from_nameless(NamelessMatchSpec::from_str(">=12", Strict).unwrap(), None);
assert!(!spec.is_virtual());
}
}
41 changes: 41 additions & 0 deletions crates/rattler_lock/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -419,6 +419,12 @@ impl<'lock> Environment<'lock> {
.map(|pkgs| pkgs.filter_map(LockedPackageRef::as_pypi))
}

/// Returns whether this environment has any pypi packages for the specified platform.
pub fn has_pypi_packages(&self, platform: Platform) -> bool {
self.pypi_packages(platform)
.is_some_and(|mut packages| packages.next().is_some())
}

/// Creates a [`OwnedEnvironment`] from this environment.
pub fn to_owned(self) -> OwnedEnvironment {
OwnedEnvironment {
Expand Down Expand Up @@ -600,4 +606,39 @@ mod test {
.map(|p| p.location().to_string())
.collect::<Vec<_>>());
}

#[test]
fn test_has_pypi_packages() {
// v4
let path = Path::new(env!("CARGO_MANIFEST_DIR"))
.join("../../test-data/conda-lock")
.join("v4/pypi-matplotlib-lock.yml");
let conda_lock = LockFile::from_path(&path).unwrap();

assert!(conda_lock
.environment(DEFAULT_ENVIRONMENT_NAME)
.unwrap()
.has_pypi_packages(Platform::Linux64));

// v6
let path = Path::new(env!("CARGO_MANIFEST_DIR"))
.join("../../test-data/conda-lock")
.join("v6/numpy-as-pypi-lock.yml");
let conda_lock = LockFile::from_path(&path).unwrap();

assert!(conda_lock
.environment(DEFAULT_ENVIRONMENT_NAME)
.unwrap()
.has_pypi_packages(Platform::OsxArm64));

let path = Path::new(env!("CARGO_MANIFEST_DIR"))
.join("../../test-data/conda-lock")
.join("v6/python-from-conda-only-lock.yml");
let conda_lock = LockFile::from_path(&path).unwrap();

assert!(!conda_lock
.environment(DEFAULT_ENVIRONMENT_NAME)
.unwrap()
.has_pypi_packages(Platform::OsxArm64));
}
}
8 changes: 7 additions & 1 deletion crates/rattler_virtual_packages/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ pub mod linux;
pub mod osx;

use std::{
env,
env, fmt,
hash::{Hash, Hasher},
str::FromStr,
sync::Arc,
Expand Down Expand Up @@ -397,6 +397,12 @@ impl EnvOverride for LibC {
}
}

impl fmt::Display for LibC {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}={}", self.family, self.version)
}
}

/// Cuda virtual package description
#[derive(Clone, Eq, PartialEq, Hash, Debug, Deserialize)]
pub struct Cuda {
Expand Down
Loading

0 comments on commit 899ab59

Please sign in to comment.