From 5c0acb925ccb873eb5a0c3e77e78de78f8d07352 Mon Sep 17 00:00:00 2001 From: Pavel Zwerschke Date: Thu, 26 Jun 2025 14:34:19 +0200 Subject: [PATCH 01/18] feat: Add glob support for package name in matchspec --- crates/rattler_conda_types/src/lib.rs | 3 +- .../rattler_conda_types/src/match_spec/mod.rs | 68 +++++- .../src/match_spec/package_name_matcher.rs | 212 ++++++++++++++++++ .../src/match_spec/parse.rs | 27 ++- .../rattler_conda_types/src/package_name.rs | 2 +- .../rattler_conda_types/src/repo_data/mod.rs | 7 +- crates/rattler_lock/src/conda.rs | 2 +- .../src/utils/serde/match_spec_map_or_vec.rs | 4 +- .../src/gateway/query.rs | 12 +- .../src/sparse/mod.rs | 7 +- crates/rattler_solve/src/libsolv_c/mod.rs | 4 +- crates/rattler_solve/src/resolvo/mod.rs | 27 ++- 12 files changed, 325 insertions(+), 50 deletions(-) create mode 100644 crates/rattler_conda_types/src/match_spec/package_name_matcher.rs diff --git a/crates/rattler_conda_types/src/lib.rs b/crates/rattler_conda_types/src/lib.rs index 5f1d5446a..08519d3d0 100644 --- a/crates/rattler_conda_types/src/lib.rs +++ b/crates/rattler_conda_types/src/lib.rs @@ -7,7 +7,8 @@ mod build_spec; mod channel; mod channel_data; mod explicit_environment_spec; -mod match_spec; +/// todo +pub mod match_spec; pub mod menuinst; mod no_arch_type; mod parse_mode; diff --git a/crates/rattler_conda_types/src/match_spec/mod.rs b/crates/rattler_conda_types/src/match_spec/mod.rs index debea457d..672e353cb 100644 --- a/crates/rattler_conda_types/src/match_spec/mod.rs +++ b/crates/rattler_conda_types/src/match_spec/mod.rs @@ -17,10 +17,15 @@ use url::Url; use crate::Channel; use crate::ChannelConfig; +/// todo pub mod matcher; +/// todo +pub mod package_name_matcher; +/// todo pub mod parse; use matcher::StringMatcher; +use package_name_matcher::PackageNameMatcher; /// A [`MatchSpec`] is, fundamentally, a query language for conda packages. Any of the fields that /// comprise a [`crate::PackageRecord`] can be used to compose a [`MatchSpec`]. @@ -130,7 +135,7 @@ use matcher::StringMatcher; #[derive(Debug, Default, Clone, Serialize, Eq, PartialEq, Hash)] pub struct MatchSpec { /// The name of the package - pub name: Option, + pub name: Option, /// The version spec of the package (e.g. `1.2.3`, `>=1.2.3`, `1.2.*`) pub version: Option, /// The build string of the package (e.g. `py37_0`, `py37h6de7cb9_0`, `py*`) @@ -177,7 +182,7 @@ impl Display for MatchSpec { } match &self.name { - Some(name) => write!(f, "{}", name.as_normalized())?, + Some(name) => write!(f, "{name}")?, None => write!(f, "*")?, } @@ -229,7 +234,7 @@ impl Display for MatchSpec { impl MatchSpec { /// Decomposes this instance into a [`NamelessMatchSpec`] and a name. - pub fn into_nameless(self) -> (Option, NamelessMatchSpec) { + pub fn into_nameless(self) -> (Option, NamelessMatchSpec) { ( self.name, NamelessMatchSpec { @@ -252,10 +257,13 @@ 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. + /// Matching both virtual and non-virtual packages is considered not virtual. pub fn is_virtual(&self) -> bool { - self.name - .as_ref() - .is_some_and(|name| name.as_normalized().starts_with("__")) + self.name.as_ref().is_some_and(|name| match name { + PackageNameMatcher::Exact(name) => name.as_normalized().starts_with("__"), + PackageNameMatcher::Glob(pattern) => pattern.as_str().starts_with("__"), + PackageNameMatcher::Regex(regex) => regex.as_str().starts_with(r"^__"), + }) } } @@ -263,7 +271,7 @@ impl MatchSpec { impl From for MatchSpec { fn from(value: PackageName) -> Self { Self { - name: Some(value), + name: Some(PackageNameMatcher::Exact(value)), ..Default::default() } } @@ -354,7 +362,7 @@ impl From for NamelessMatchSpec { impl MatchSpec { /// Constructs a [`MatchSpec`] from a [`NamelessMatchSpec`] and a name. - pub fn from_nameless(spec: NamelessMatchSpec, name: Option) -> Self { + pub fn from_nameless(spec: NamelessMatchSpec, name: Option) -> Self { Self { name, version: spec.version, @@ -450,7 +458,7 @@ impl Matches for MatchSpec { /// Match a [`MatchSpec`] against a [`PackageRecord`] fn matches(&self, other: &PackageRecord) -> bool { if let Some(name) = self.name.as_ref() { - if name != &other.name { + if !name.matches(&other.name) { return false; } } @@ -533,7 +541,7 @@ impl Matches 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 { + if !name.matches(&other.name) { return false; } } @@ -590,8 +598,8 @@ impl TryFrom for MatchSpec { .ok_or(MatchSpecUrlError::InvalidFilename(filename.to_string()))?; spec.name = Some( - PackageName::from_str(&archive_identifier.name) - .map_err(|_err| MatchSpecUrlError::InvalidPackageName(archive_identifier.name))?, + PackageNameMatcher::from_str(&archive_identifier.name) + .map_err(|e| MatchSpecUrlError::InvalidPackageName(e.to_string()))?, ); Ok(spec) @@ -978,4 +986,40 @@ mod tests { MatchSpec::from_nameless(NamelessMatchSpec::from_str(">=12", Strict).unwrap(), None); assert!(!spec.is_virtual()); } + + #[test] + fn test_glob_in_name() { + let spec = MatchSpec::from_str("foo* >=12", Strict).unwrap(); + assert!(spec.matches(&PackageRecord::new( + PackageName::from_str("foo").unwrap(), + Version::from_str("13.0").unwrap(), + String::from(""), + ))); + assert!(!spec.matches(&PackageRecord::new( + PackageName::from_str("foo").unwrap(), + Version::from_str("11.0").unwrap(), + String::from(""), + ))); + assert!(spec.matches(&PackageRecord::new( + PackageName::from_str("foo-bar").unwrap(), + Version::from_str("12.0").unwrap(), + String::from(""), + ))); + + let spec = MatchSpec::from_str("foo* >=12[license=MIT]", Strict).unwrap(); + assert!(!spec.matches(&PackageRecord::new( + PackageName::from_str("foo-bar").unwrap(), + Version::from_str("12.0").unwrap(), + String::from(""), + ))); + assert!(spec.matches(&{ + let mut record = PackageRecord::new( + PackageName::from_str("foo-bar").unwrap(), + Version::from_str("12.0").unwrap(), + String::from(""), + ); + record.license = Some("MIT".into()); + record + })); + } } diff --git a/crates/rattler_conda_types/src/match_spec/package_name_matcher.rs b/crates/rattler_conda_types/src/match_spec/package_name_matcher.rs new file mode 100644 index 000000000..c536d9f14 --- /dev/null +++ b/crates/rattler_conda_types/src/match_spec/package_name_matcher.rs @@ -0,0 +1,212 @@ +use std::{ + borrow::Cow, + fmt::{Display, Formatter}, + hash::{Hash, Hasher}, + str::FromStr, +}; + +use serde::{Deserialize, Deserializer, Serialize, Serializer}; + +use crate::{InvalidPackageNameError, PackageName}; + +/// Match a given string either by exact match, glob or regex +#[derive(Debug, Clone)] +pub enum PackageNameMatcher { + /// Match the string exactly + Exact(PackageName), + /// Match the string by glob. A glob uses a * to match any characters. + /// For example, `*` matches any string, `foo*` matches any string starting + /// with `foo`, `*bar` matches any string ending with `bar` and `foo*bar` + /// matches any string starting with `foo` and ending with `bar`. + Glob(glob::Pattern), + /// Match the string by regex. A regex starts with a `^`, ends with a `$` + /// and uses the regex syntax. For example, `^foo.*bar$` matches any + /// string starting with `foo` and ending with `bar`. Note that the regex + /// is anchored, so it must match the entire string. + Regex(regex::Regex), +} + +impl Hash for PackageNameMatcher { + fn hash(&self, state: &mut H) { + match self { + PackageNameMatcher::Exact(s) => s.hash(state), + PackageNameMatcher::Glob(pattern) => pattern.hash(state), + PackageNameMatcher::Regex(regex) => regex.as_str().hash(state), + } + } +} + +impl PartialEq for PackageNameMatcher { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (PackageNameMatcher::Exact(s1), PackageNameMatcher::Exact(s2)) => s1 == s2, + (PackageNameMatcher::Glob(s1), PackageNameMatcher::Glob(s2)) => { + s1.as_str() == s2.as_str() + } + (PackageNameMatcher::Regex(s1), PackageNameMatcher::Regex(s2)) => { + s1.as_str() == s2.as_str() + } + _ => false, + } + } +} + +impl PackageNameMatcher { + /// Match string against [`PackageNameMatcher`]. + pub fn matches(&self, other: &PackageName) -> bool { + match self { + PackageNameMatcher::Exact(s) => s == other, + PackageNameMatcher::Glob(glob) => glob.matches(other.as_normalized()), + PackageNameMatcher::Regex(regex) => regex.is_match(other.as_normalized()), + } + } +} + +/// Error when parsing [`PackageNameMatcher`] +#[derive(Debug, Clone, Eq, PartialEq, thiserror::Error)] +pub enum PackageNameMatcherParseError { + /// Could not parse the string as a glob + #[error("invalid glob: {glob}")] + Glob { + /// The invalid glob + glob: String, + }, + + /// Could not parse the string as a regex + #[error("invalid regex: {regex}")] + Regex { + /// The invalid regex + regex: String, + }, + + /// Could not parse the string as a package name + #[error("invalid package name {name}: {source}")] + PackageName { + /// The invalid package name + name: String, + + /// The source error + source: InvalidPackageNameError, + }, +} + +impl FromStr for PackageNameMatcher { + type Err = PackageNameMatcherParseError; + + fn from_str(s: &str) -> Result { + if s.starts_with('^') && s.ends_with('$') { + Ok(PackageNameMatcher::Regex(regex::Regex::new(s).map_err( + |_err| PackageNameMatcherParseError::Regex { + regex: s.to_string(), + }, + )?)) + } else if s.contains('*') { + Ok(PackageNameMatcher::Glob(glob::Pattern::new(s).map_err( + |_err| PackageNameMatcherParseError::Glob { + glob: s.to_string(), + }, + )?)) + } else { + Ok(PackageNameMatcher::Exact( + PackageName::from_str(s).map_err(|e| { + PackageNameMatcherParseError::PackageName { + name: s.to_string(), + source: e, + } + })?, + )) + } + } +} + +impl Display for PackageNameMatcher { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + PackageNameMatcher::Exact(s) => write!(f, "{}", s.as_normalized()), + PackageNameMatcher::Glob(s) => write!(f, "{}", s.as_str()), + PackageNameMatcher::Regex(s) => write!(f, "{}", s.as_str()), + } + } +} + +impl Eq for PackageNameMatcher {} + +impl Serialize for PackageNameMatcher { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + match self { + PackageNameMatcher::Exact(s) => s.serialize(serializer), + PackageNameMatcher::Glob(s) => s.as_str().serialize(serializer), + PackageNameMatcher::Regex(s) => s.as_str().serialize(serializer), + } + } +} + +impl<'de> Deserialize<'de> for PackageNameMatcher { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let s = Cow::<'de, str>::deserialize(deserializer)?; + PackageNameMatcher::from_str(&s).map_err(serde::de::Error::custom) + } +} + +/// Error when converting a [`PackageNameMatcher`] to a [`PackageName`] +#[derive(Debug, Clone, Eq, PartialEq, thiserror::Error)] +pub enum IntoPackageNameError { + /// The package name matcher is not an exact package name + #[error("not an exact package name")] + NotExact, +} + +impl TryInto for PackageNameMatcher { + type Error = IntoPackageNameError; + fn try_into(self) -> Result { + match self { + PackageNameMatcher::Exact(name) => Ok(name), + _ => Err(IntoPackageNameError::NotExact), + } + } +} + +impl TryInto for &PackageNameMatcher { + type Error = IntoPackageNameError; + fn try_into(self) -> Result { + match self { + PackageNameMatcher::Exact(name) => Ok(name.clone()), + _ => Err(IntoPackageNameError::NotExact), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_package_name_matcher() { + assert_eq!( + PackageNameMatcher::Exact(PackageName::from_str("foo").unwrap()), + "foo".parse().unwrap() + ); + assert_eq!( + PackageNameMatcher::Glob(glob::Pattern::new("foo*").unwrap()), + "foo*bar".parse().unwrap() + ); + assert_eq!( + PackageNameMatcher::Regex(regex::Regex::new("^foo.*$").unwrap()), + "^foo.*$".parse().unwrap() + ); + } +} + +/// todo +pub fn package_name_matcher_to_package_name(matcher: &PackageNameMatcher) -> PackageName { + match matcher { + PackageNameMatcher::Exact(name) => name.clone(), + _ => panic!("Packages with no name are not supported"), + } +} diff --git a/crates/rattler_conda_types/src/match_spec/parse.rs b/crates/rattler_conda_types/src/match_spec/parse.rs index a88b2ce99..795119e89 100644 --- a/crates/rattler_conda_types/src/match_spec/parse.rs +++ b/crates/rattler_conda_types/src/match_spec/parse.rs @@ -22,6 +22,7 @@ use super::{ }; use crate::{ build_spec::{BuildNumberSpec, ParseBuildNumberSpecError}, + match_spec::package_name_matcher::{PackageNameMatcher, PackageNameMatcherParseError}, package::ArchiveIdentifier, utils::{path::is_absolute_path, url::parse_scheme}, version_spec::{ @@ -29,9 +30,8 @@ use crate::{ version_tree::{recognize_constraint, recognize_version}, ParseVersionSpecError, }, - Channel, ChannelConfig, InvalidPackageNameError, NamelessMatchSpec, PackageName, - ParseChannelError, ParseStrictness, - ParseStrictness::{Lenient, Strict}, + Channel, ChannelConfig, InvalidPackageNameError, NamelessMatchSpec, ParseChannelError, + ParseStrictness::{self, Lenient, Strict}, ParseVersionError, Platform, VersionSpec, }; @@ -99,6 +99,10 @@ pub enum ParseMatchSpecError { #[error(transparent)] InvalidPackageName(#[from] InvalidPackageNameError), + /// The package name matcher was invalid + #[error(transparent)] + InvalidPackageNameMatcher(#[from] PackageNameMatcherParseError), + /// Multiple values for a key in the matchspec #[error("found multiple values for: {0}")] MultipleValueForKey(String), @@ -361,7 +365,9 @@ pub fn parse_url_like(input: &str) -> Result, ParseMatchSpecError> { } /// Strip the package name from the input. -fn strip_package_name(input: &str) -> Result<(Option, &str), ParseMatchSpecError> { +fn strip_package_name( + input: &str, +) -> Result<(Option, &str), ParseMatchSpecError> { let (rest, package_name) = take_while1(|c: char| !c.is_whitespace() && !is_start_of_version_constraint(c))( input.trim(), @@ -374,14 +380,19 @@ fn strip_package_name(input: &str) -> Result<(Option, &str), ParseM return Err(ParseMatchSpecError::MissingPackageName); } + let rest = rest.trim(); + // Handle asterisk as a wildcard (no package name) if trimmed_package_name == "*" { - return Ok((None, rest.trim())); + return Ok((None, rest)); } Ok(( - Some(PackageName::from_str(trimmed_package_name)?), - rest.trim(), + Some( + PackageNameMatcher::from_str(trimmed_package_name) + .map_err(ParseMatchSpecError::InvalidPackageNameMatcher)?, + ), + rest, )) } @@ -627,7 +638,7 @@ fn matchspec_parser( if nameless_match_spec.url.is_none() { if let Some(url) = parse_url_like(&input)? { let archive = ArchiveIdentifier::try_from_url(&url); - let name = archive.and_then(|a| a.try_into().ok()); + let name = archive.and_then(|a| PackageNameMatcher::from_str(&a.name).ok()); // TODO: This should also work without a proper name from the url filename if name.is_none() { diff --git a/crates/rattler_conda_types/src/package_name.rs b/crates/rattler_conda_types/src/package_name.rs index 1383ace27..998749033 100644 --- a/crates/rattler_conda_types/src/package_name.rs +++ b/crates/rattler_conda_types/src/package_name.rs @@ -48,7 +48,7 @@ impl PackageName { } /// An error that is returned when conversion from a string to a [`PackageName`] fails. -#[derive(Clone, Debug, Error, PartialEq)] +#[derive(Clone, Debug, Error, Eq, PartialEq)] pub enum InvalidPackageNameError { /// The package name contains illegal characters #[error("'{0}' is not a valid package name. Package names can only contain 0-9, a-z, A-Z, -, _, or .")] diff --git a/crates/rattler_conda_types/src/repo_data/mod.rs b/crates/rattler_conda_types/src/repo_data/mod.rs index 790cbd9d4..7d5bbeb3a 100644 --- a/crates/rattler_conda_types/src/repo_data/mod.rs +++ b/crates/rattler_conda_types/src/repo_data/mod.rs @@ -386,9 +386,10 @@ impl PackageRecord { // Then we check if all constraints are satisfied. for constraint in package.constrains.iter() { let constraint_spec = MatchSpec::from_str(constraint, ParseStrictness::Lenient)?; - let matching_package = records - .iter() - .find(|record| Some(record.as_ref().name.clone()) == constraint_spec.name); + let matching_package = records.iter().find(|record| match &constraint_spec.name { + Some(matcher) => matcher.matches(&record.as_ref().name), + None => false, + }); if matching_package.is_some_and(|p| !constraint_spec.matches(p.as_ref())) { return Err(ValidatePackageRecordsError::PackageConstraintNotSatisfied { package: package.to_owned(), diff --git a/crates/rattler_lock/src/conda.rs b/crates/rattler_lock/src/conda.rs index 01081ed16..5cfd09eaa 100644 --- a/crates/rattler_lock/src/conda.rs +++ b/crates/rattler_lock/src/conda.rs @@ -281,7 +281,7 @@ impl Matches for CondaPackageData { fn matches(&self, spec: &MatchSpec) -> bool { // Check if the name matches if let Some(name) = &spec.name { - if name != &self.record().name { + if !name.matches(&self.record().name) { return false; } } diff --git a/crates/rattler_lock/src/utils/serde/match_spec_map_or_vec.rs b/crates/rattler_lock/src/utils/serde/match_spec_map_or_vec.rs index 2ef6bd27a..174b1d868 100644 --- a/crates/rattler_lock/src/utils/serde/match_spec_map_or_vec.rs +++ b/crates/rattler_lock/src/utils/serde/match_spec_map_or_vec.rs @@ -1,6 +1,6 @@ use fxhash::FxBuildHasher; use indexmap::IndexMap; -use rattler_conda_types::{MatchSpec, NamelessMatchSpec, PackageName}; +use rattler_conda_types::{match_spec::package_name_matcher::PackageNameMatcher, MatchSpec, NamelessMatchSpec, PackageName}; use serde::{Deserialize, Deserializer}; use serde_with::{serde_as, DeserializeAs, DisplayFromStr}; @@ -26,7 +26,7 @@ impl<'de> DeserializeAs<'de, Vec> for MatchSpecMapOrVec { MapOrVec::Vec(v) => v, MapOrVec::Map(m) => m .into_iter() - .map(|(name, spec)| MatchSpec::from_nameless(spec, Some(name)).to_string()) + .map(|(name, spec)| MatchSpec::from_nameless(spec, Some(PackageNameMatcher::Exact(name))).to_string()) .collect(), }) } diff --git a/crates/rattler_repodata_gateway/src/gateway/query.rs b/crates/rattler_repodata_gateway/src/gateway/query.rs index 4b2433bdc..53d9dd34e 100644 --- a/crates/rattler_repodata_gateway/src/gateway/query.rs +++ b/crates/rattler_repodata_gateway/src/gateway/query.rs @@ -6,7 +6,7 @@ use std::{ use futures::{select_biased, stream::FuturesUnordered, FutureExt, StreamExt}; use itertools::Itertools; -use rattler_conda_types::{Channel, MatchSpec, Matches, PackageName, Platform}; +use rattler_conda_types::{match_spec::package_name_matcher::package_name_matcher_to_package_name, Channel, MatchSpec, Matches, PackageName, Platform}; use super::{subdir::Subdir, BarrierCell, GatewayError, GatewayInner, RepoData}; use crate::Reporter; @@ -118,12 +118,12 @@ impl RepoDataQuery { .name .clone() .ok_or(GatewayError::MatchSpecWithoutName(Box::new(spec.clone())))?; - seen.insert(name.clone()); + seen.insert(package_name_matcher_to_package_name(&name.clone())); direct_url_specs.push((spec.clone(), url, name)); } else if let Some(name) = &spec.name { - seen.insert(name.clone()); + seen.insert(package_name_matcher_to_package_name(&name.clone())); let pending = pending_package_specs - .entry(name.clone()) + .entry(package_name_matcher_to_package_name(&name.clone())) .or_insert_with(|| SourceSpecs::Input(vec![])); let SourceSpecs::Input(input_specs) = pending else { panic!("RootSpecs::Input was overwritten by RootSpecs::Transitive"); @@ -197,11 +197,11 @@ impl RepoDataQuery { // Check if record actually has the same name if let Some(record) = record.first() { - if record.package_record.name != name { + if !name.matches(&record.package_record.name) { // Using as_source to get the closest to the retrieved input. return Err(GatewayError::UrlRecordNameMismatch( record.package_record.name.as_source().to_string(), - name.as_source().to_string(), + name.to_string(), )); } } diff --git a/crates/rattler_repodata_gateway/src/sparse/mod.rs b/crates/rattler_repodata_gateway/src/sparse/mod.rs index ec2b28c18..1a3495993 100644 --- a/crates/rattler_repodata_gateway/src/sparse/mod.rs +++ b/crates/rattler_repodata_gateway/src/sparse/mod.rs @@ -7,8 +7,7 @@ use bytes::Bytes; use fs_err as fs; use itertools::Itertools; use rattler_conda_types::{ - compute_package_url, package::ArchiveType, Channel, ChannelInfo, MatchSpec, Matches, - PackageName, PackageRecord, RepoDataRecord, + compute_package_url, match_spec::package_name_matcher::PackageNameMatcher, package::ArchiveType, Channel, ChannelInfo, MatchSpec, Matches, PackageName, PackageRecord, RepoDataRecord }; use rattler_redaction::Redact; use serde::{ @@ -259,6 +258,10 @@ impl SparseRepoData { let base_url = repo_data.info.as_ref().and_then(|i| i.base_url.as_deref()); for (package_name, specs) in &spec.into_iter().chunk_by(|spec| spec.borrow().name.clone()) { let grouped_specs = specs.into_iter().collect::>(); + let package_name = match package_name { + Some(PackageNameMatcher::Exact(name)) => Some(name), + _ => None, // todo: this can lead to unexpected results in some scenarios + }; let mut parsed_records = parse_records( package_name.as_ref(), &repo_data.packages, diff --git a/crates/rattler_solve/src/libsolv_c/mod.rs b/crates/rattler_solve/src/libsolv_c/mod.rs index b643b2e0e..3899c7429 100644 --- a/crates/rattler_solve/src/libsolv_c/mod.rs +++ b/crates/rattler_solve/src/libsolv_c/mod.rs @@ -10,7 +10,7 @@ pub use input::cache_repodata; use input::{add_repodata_records, add_solv_file, add_virtual_packages}; pub use libc_byte_slice::LibcByteSlice; use output::get_required_packages; -use rattler_conda_types::{MatchSpec, NamelessMatchSpec, RepoDataRecord, SolverResult}; +use rattler_conda_types::{match_spec::package_name_matcher::PackageNameMatcher, MatchSpec, NamelessMatchSpec, RepoDataRecord, SolverResult}; use wrapper::{ flags::SolverFlag, pool::{Pool, Verbosity}, @@ -254,7 +254,7 @@ impl super::SolverImpl for Solver { for virtual_package in task.virtual_packages { let id = pool.intern_matchspec(&MatchSpec::from_nameless( NamelessMatchSpec::default(), - Some(virtual_package.name), + Some(PackageNameMatcher::Exact(virtual_package.name)), )); goal.install(id, false); } diff --git a/crates/rattler_solve/src/resolvo/mod.rs b/crates/rattler_solve/src/resolvo/mod.rs index 353ba678f..d8e37b5fb 100644 --- a/crates/rattler_solve/src/resolvo/mod.rs +++ b/crates/rattler_solve/src/resolvo/mod.rs @@ -13,9 +13,7 @@ use chrono::{DateTime, Utc}; use conda_sorting::SolvableSorter; use itertools::Itertools; use rattler_conda_types::{ - package::ArchiveType, version_spec::EqualityOperator, BuildNumberSpec, GenericVirtualPackage, - MatchSpec, Matches, NamelessMatchSpec, OrdOperator, PackageName, PackageRecord, - ParseMatchSpecError, ParseStrictness, RepoDataRecord, SolverResult, StringMatcher, VersionSpec, + match_spec::package_name_matcher::{package_name_matcher_to_package_name, PackageNameMatcher}, package::ArchiveType, version_spec::EqualityOperator, BuildNumberSpec, GenericVirtualPackage, MatchSpec, Matches, NamelessMatchSpec, OrdOperator, PackageName, PackageRecord, ParseMatchSpecError, ParseStrictness, RepoDataRecord, SolverResult, StringMatcher, VersionSpec }; use resolvo::{ utils::{Pool, VersionSet}, @@ -293,7 +291,7 @@ impl<'a> CondaDependencyProvider<'a> { let direct_dependencies = match_specs .iter() .filter_map(|spec| spec.name.as_ref()) - .map(|name| pool.intern_package_name(name.as_normalized())) + .map(|name| pool.intern_package_name(package_name_matcher_to_package_name(name).as_normalized())) .collect(); // TODO: Normalize these channel names to urls so we can compare them correctly. @@ -424,9 +422,10 @@ impl<'a> CondaDependencyProvider<'a> { // Add to excluded when package is not in the specified channel. if !channel_specific_specs.is_empty() { if let Some(spec) = channel_specific_specs.iter().find(|&&spec| { - spec.name + package_name_matcher_to_package_name(spec.name .as_ref() .expect("expecting a name") + ) .as_normalized() == record.package_record.name.as_normalized() }) { @@ -668,7 +667,7 @@ impl DependencyProvider for CondaDependencyProvider<'_> { // Add a dependency back to the base package with exact version let base_spec = MatchSpec { - name: Some(record.package_record.name.clone()), + name: Some(PackageNameMatcher::Exact(record.package_record.name.clone())), version: Some(VersionSpec::Exact( EqualityOperator::Equals, record.package_record.version.version().clone(), @@ -687,7 +686,7 @@ impl DependencyProvider for CondaDependencyProvider<'_> { let (name, nameless_spec) = base_spec.into_nameless(); let name_id = self.pool.intern_package_name( - name.expect("cannot use matchspec without a name") + package_name_matcher_to_package_name(&name.expect("cannot use matchspec without a name")) .as_normalized(), ); let version_set_id = self.pool.intern_version_set(name_id, nameless_spec.into()); @@ -845,6 +844,10 @@ impl super::SolverImpl for Solver { let (name, nameless_spec) = spec.clone().into_nameless(); let features = &spec.extras; let name = name.expect("cannot use matchspec without a name"); + let name = match name { + PackageNameMatcher::Exact(name) => name, + _ => panic!("Packages with no name are not supported"), + }; let name_id = provider.pool.intern_package_name(name.as_normalized()); let mut reqs = vec![provider .pool @@ -884,7 +887,7 @@ impl super::SolverImpl for Solver { .map(|spec| { let (name, spec) = spec.clone().into_nameless(); let name = name.expect("cannot use matchspec without a name"); - let name_id = provider.pool.intern_package_name(name.as_normalized()); + let name_id = provider.pool.intern_package_name(package_name_matcher_to_package_name(&name).as_normalized()); provider.pool.intern_version_set(name_id, spec.into()) }) .collect(); @@ -947,8 +950,8 @@ fn parse_match_spec( for feature in features { let name_with_feature = NameType::BaseWithFeature( - name.as_ref() - .expect("Packages with no name are not supported") + package_name_matcher_to_package_name(name.as_ref() + .expect("Packages with no name are not supported")) .as_normalized() .to_owned(), feature.to_string(), @@ -963,9 +966,9 @@ fn parse_match_spec( } } else { let dependency_name = pool.intern_package_name( - name.as_ref() + package_name_matcher_to_package_name(name.as_ref() .expect("Packages with no name are not supported") - .as_normalized(), + ).as_normalized(), ); let version_set_id = pool.intern_version_set(dependency_name, spec.into()); version_set_ids.push(version_set_id); From 6620acb2d5d2c119db07ee25cc95a827cae9bc3c Mon Sep 17 00:00:00 2001 From: Pavel Zwerschke Date: Fri, 26 Sep 2025 17:58:34 +0200 Subject: [PATCH 02/18] wip --- crates/rattler_solve/src/resolvo/mod.rs | 29 ++++++++++++++++--------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/crates/rattler_solve/src/resolvo/mod.rs b/crates/rattler_solve/src/resolvo/mod.rs index 787d22c60..c1b7b5514 100644 --- a/crates/rattler_solve/src/resolvo/mod.rs +++ b/crates/rattler_solve/src/resolvo/mod.rs @@ -12,8 +12,10 @@ use chrono::{DateTime, Utc}; use conda_sorting::SolvableSorter; use itertools::Itertools; use rattler_conda_types::{ - package::ArchiveType, GenericVirtualPackage, MatchSpec, Matches, NamelessMatchSpec, - PackageName, ParseMatchSpecError, ParseStrictness, RepoDataRecord, SolverResult, + match_spec::package_name_matcher::{package_name_matcher_to_package_name, PackageNameMatcher}, + package::ArchiveType, + GenericVirtualPackage, MatchSpec, Matches, NamelessMatchSpec, PackageName, ParseMatchSpecError, + ParseStrictness, RepoDataRecord, SolverResult, }; use resolvo::{ utils::{Pool, VersionSet}, @@ -297,7 +299,8 @@ impl<'a> CondaDependencyProvider<'a> { let direct_dependencies = match_specs .iter() .filter_map(|spec| spec.name.as_ref()) - .map(|name| pool.intern_package_name(name)) + .map(package_name_matcher_to_package_name) + .map(|name| pool.intern_package_name(&name)) .collect(); // TODO: Normalize these channel names to urls so we can compare them correctly. @@ -403,10 +406,10 @@ impl<'a> CondaDependencyProvider<'a> { // Add to excluded when package is not in the specified channel. if !channel_specific_specs.is_empty() { if let Some(spec) = channel_specific_specs.iter().find(|&&spec| { - spec.name - .as_ref() - .expect("expecting a name") - .as_normalized() + package_name_matcher_to_package_name( + spec.name.as_ref().expect("expecting a name"), + ) + .as_normalized() == record.package_record.name.as_normalized() }) { // Check if the spec has a channel, and compare it to the repodata @@ -846,7 +849,9 @@ impl super::SolverImpl for Solver { let (Some(name), spec) = spec.clone().into_nameless() else { unimplemented!("matchspecs without a name are not supported"); }; - let name_id = provider.pool.intern_package_name(&name); + let name_id = provider + .pool + .intern_package_name(&package_name_matcher_to_package_name(&name)); provider.pool.intern_version_set(name_id, spec.into()) }) .collect(); @@ -923,11 +928,15 @@ fn version_sets_for_match_spec( // Add a dependency on each extra. let mut version_set_ids = vec![]; for extra in spec.extras.iter().flatten() { - version_set_ids.push(extra_version_set(pool, name.clone(), extra.clone())); + version_set_ids.push(extra_version_set( + pool, + package_name_matcher_to_package_name(&name), + extra.clone(), + )); } // Create a version set for the match spec itself. - let dependency_name = pool.intern_package_name(&name); + let dependency_name = pool.intern_package_name(&package_name_matcher_to_package_name(&name)); let version_set_id = pool.intern_version_set(dependency_name, spec.into()); version_set_ids.push(version_set_id); From d48073f9f1d1ad6ff290ad8ecb2d7d47e86eb22e Mon Sep 17 00:00:00 2001 From: Pavel Zwerschke Date: Fri, 26 Sep 2025 18:09:52 +0200 Subject: [PATCH 03/18] wip --- crates/rattler/src/install/installer/mod.rs | 3 ++- crates/rattler_solve/tests/backends.rs | 28 ++++++++++++++++----- crates/rattler_solve/tests/sorting.rs | 5 ++-- 3 files changed, 27 insertions(+), 9 deletions(-) diff --git a/crates/rattler/src/install/installer/mod.rs b/crates/rattler/src/install/installer/mod.rs index 7215fa6ec..e776250a2 100644 --- a/crates/rattler/src/install/installer/mod.rs +++ b/crates/rattler/src/install/installer/mod.rs @@ -22,6 +22,7 @@ pub use indicatif::{ use itertools::Itertools; use rattler_cache::package_cache::{CacheLock, CacheReporter}; use rattler_conda_types::{ + match_spec::package_name_matcher::{package_name_matcher_to_package_name, PackageNameMatcher}, prefix_record::{Link, LinkType}, MatchSpec, PackageName, Platform, PrefixRecord, RepoDataRecord, }; @@ -847,7 +848,7 @@ fn create_spec_mapping(specs: &[MatchSpec]) -> std::collections::HashMap(specs: Vec<&str>) -> Vec { let sparse_repo_data = read_real_world_repo_data(); - let names = specs.iter().filter_map(|s| s.name.as_ref().cloned()); + let names = specs.iter().filter_map(|s| { + s.name + .as_ref() + .map(|n| package_name_matcher_to_package_name(&n)) + }); let available_packages = SparseRepoData::load_records_recursive( sparse_repo_data, names, @@ -1156,7 +1161,11 @@ fn compare_solve(task: CompareTask<'_>) { let sparse_repo_data = read_real_world_repo_data(); - let names = specs.iter().filter_map(|s| s.name.as_ref().cloned()); + let names = specs.iter().filter_map(|s| { + s.name + .as_ref() + .map(|n| package_name_matcher_to_package_name(&n)) + }); let available_packages = SparseRepoData::load_records_recursive( sparse_repo_data, names, @@ -1292,7 +1301,11 @@ fn solve_to_get_channel_of_spec( ) { let spec = MatchSpec::from_str(spec_str, ParseStrictness::Lenient).unwrap(); let specs = vec![spec.clone()]; - let names = specs.iter().filter_map(|s| s.name.as_ref().cloned()); + let names = specs.iter().filter_map(|s| { + s.name + .as_ref() + .map(|n| package_name_matcher_to_package_name(&n)) + }); let available_packages = SparseRepoData::load_records_recursive( repo_data, @@ -1311,7 +1324,10 @@ fn solve_to_get_channel_of_spec( let result: Vec = T::default().solve(task).unwrap().records; let record = result.iter().find(|record| { - record.package_record.name.as_normalized() == spec.name.as_ref().unwrap().as_normalized() + spec.name + .as_ref() + .unwrap() + .matches(&record.package_record.name) }); assert_eq!(record.unwrap().channel, Some(expected_channel.to_string())); } diff --git a/crates/rattler_solve/tests/sorting.rs b/crates/rattler_solve/tests/sorting.rs index 54836ace0..7868057b9 100644 --- a/crates/rattler_solve/tests/sorting.rs +++ b/crates/rattler_solve/tests/sorting.rs @@ -5,7 +5,8 @@ use std::path::Path; use futures::FutureExt; use itertools::Itertools; use rattler_conda_types::{ - Channel, MatchSpec, PackageName, ParseStrictness::Lenient, RepoDataRecord, + match_spec::package_name_matcher::package_name_matcher_to_package_name, Channel, MatchSpec, + PackageName, ParseStrictness::Lenient, RepoDataRecord, }; use rattler_repodata_gateway::sparse::{PackageFormatSelection, SparseRepoData}; use rattler_solve::{resolvo::CondaDependencyProvider, ChannelPriority, SolveStrategy}; @@ -35,7 +36,7 @@ fn load_repodata(package_name: &PackageName) -> Vec> { fn create_sorting_snapshot(package_name: &str, strategy: SolveStrategy) -> String { let match_spec = MatchSpec::from_str(package_name, Lenient).unwrap(); - let package_name = match_spec.name.clone().unwrap(); + let package_name = package_name_matcher_to_package_name(&match_spec.name.clone().unwrap()); // Load repodata let repodata = load_repodata(&package_name); From 7d8936388b2897e7b881c972904e282a45dc74ea Mon Sep 17 00:00:00 2001 From: Pavel Zwerschke Date: Fri, 26 Sep 2025 18:32:40 +0200 Subject: [PATCH 04/18] refactor --- crates/rattler/src/install/installer/mod.rs | 3 +-- .../src/match_spec/package_name_matcher.rs | 24 +++++++++++------ .../src/utils/serde/match_spec_map_or_vec.rs | 9 +++++-- .../src/gateway/query.rs | 10 +++---- .../src/sparse/mod.rs | 4 ++- crates/rattler_solve/src/libsolv_c/mod.rs | 5 +++- crates/rattler_solve/src/resolvo/mod.rs | 26 ++++++++----------- 7 files changed, 47 insertions(+), 34 deletions(-) diff --git a/crates/rattler/src/install/installer/mod.rs b/crates/rattler/src/install/installer/mod.rs index e776250a2..3038c6f41 100644 --- a/crates/rattler/src/install/installer/mod.rs +++ b/crates/rattler/src/install/installer/mod.rs @@ -22,7 +22,6 @@ pub use indicatif::{ use itertools::Itertools; use rattler_cache::package_cache::{CacheLock, CacheReporter}; use rattler_conda_types::{ - match_spec::package_name_matcher::{package_name_matcher_to_package_name, PackageNameMatcher}, prefix_record::{Link, LinkType}, MatchSpec, PackageName, Platform, PrefixRecord, RepoDataRecord, }; @@ -848,7 +847,7 @@ fn create_spec_mapping(specs: &[MatchSpec]) -> std::collections::HashMap regex.is_match(other.as_normalized()), } } + + /// Convert [`PackageNameMatcher`] to [`PackageName`]. + pub fn into_exact(self) -> PackageName { + match self { + PackageNameMatcher::Exact(s) => s, + _ => panic!("PackageNameMatcher is not Exact"), + } + } + + /// Convert [`PackageNameMatcher`] to [`PackageName`]. + pub fn as_exact(&self) -> &PackageName { + match self { + PackageNameMatcher::Exact(s) => s, + _ => panic!("PackageNameMatcher is not Exact"), + } + } } /// Error when parsing [`PackageNameMatcher`] @@ -202,11 +218,3 @@ mod tests { ); } } - -/// todo -pub fn package_name_matcher_to_package_name(matcher: &PackageNameMatcher) -> PackageName { - match matcher { - PackageNameMatcher::Exact(name) => name.clone(), - _ => panic!("Packages with no name are not supported"), - } -} diff --git a/crates/rattler_lock/src/utils/serde/match_spec_map_or_vec.rs b/crates/rattler_lock/src/utils/serde/match_spec_map_or_vec.rs index 174b1d868..d2b745dde 100644 --- a/crates/rattler_lock/src/utils/serde/match_spec_map_or_vec.rs +++ b/crates/rattler_lock/src/utils/serde/match_spec_map_or_vec.rs @@ -1,6 +1,8 @@ use fxhash::FxBuildHasher; use indexmap::IndexMap; -use rattler_conda_types::{match_spec::package_name_matcher::PackageNameMatcher, MatchSpec, NamelessMatchSpec, PackageName}; +use rattler_conda_types::{ + match_spec::package_name_matcher::PackageNameMatcher, MatchSpec, NamelessMatchSpec, PackageName, +}; use serde::{Deserialize, Deserializer}; use serde_with::{serde_as, DeserializeAs, DisplayFromStr}; @@ -26,7 +28,10 @@ impl<'de> DeserializeAs<'de, Vec> for MatchSpecMapOrVec { MapOrVec::Vec(v) => v, MapOrVec::Map(m) => m .into_iter() - .map(|(name, spec)| MatchSpec::from_nameless(spec, Some(PackageNameMatcher::Exact(name))).to_string()) + .map(|(name, spec)| { + MatchSpec::from_nameless(spec, Some(PackageNameMatcher::Exact(name))) + .to_string() + }) .collect(), }) } diff --git a/crates/rattler_repodata_gateway/src/gateway/query.rs b/crates/rattler_repodata_gateway/src/gateway/query.rs index 0f9cd5cb9..ce03b6608 100644 --- a/crates/rattler_repodata_gateway/src/gateway/query.rs +++ b/crates/rattler_repodata_gateway/src/gateway/query.rs @@ -6,7 +6,7 @@ use std::{ use futures::{select_biased, stream::FuturesUnordered, FutureExt, StreamExt}; use itertools::Itertools; -use rattler_conda_types::{match_spec::package_name_matcher::package_name_matcher_to_package_name, Channel, MatchSpec, Matches, PackageName, Platform}; +use rattler_conda_types::{Channel, MatchSpec, Matches, PackageName, Platform}; use super::{subdir::Subdir, BarrierCell, GatewayError, GatewayInner, RepoData}; use crate::Reporter; @@ -118,12 +118,12 @@ impl RepoDataQuery { .name .clone() .ok_or(GatewayError::MatchSpecWithoutName(Box::new(spec.clone())))?; - seen.insert(package_name_matcher_to_package_name(&name.clone())); - direct_url_specs.push((spec.clone(), url, name)); + seen.insert(name.clone().into_exact()); + direct_url_specs.push((spec.clone(), url, name.clone())); } else if let Some(name) = &spec.name { - seen.insert(package_name_matcher_to_package_name(&name.clone())); + seen.insert(name.clone().into_exact()); let pending = pending_package_specs - .entry(package_name_matcher_to_package_name(&name.clone())) + .entry(name.clone().into_exact()) .or_insert_with(|| SourceSpecs::Input(vec![])); let SourceSpecs::Input(input_specs) = pending else { panic!("RootSpecs::Input was overwritten by RootSpecs::Transitive"); diff --git a/crates/rattler_repodata_gateway/src/sparse/mod.rs b/crates/rattler_repodata_gateway/src/sparse/mod.rs index 8eb6f8a02..d544db708 100644 --- a/crates/rattler_repodata_gateway/src/sparse/mod.rs +++ b/crates/rattler_repodata_gateway/src/sparse/mod.rs @@ -15,7 +15,9 @@ use bytes::Bytes; use fs_err as fs; use itertools::Itertools; use rattler_conda_types::{ - compute_package_url, match_spec::package_name_matcher::PackageNameMatcher, package::ArchiveType, Channel, ChannelInfo, MatchSpec, Matches, PackageName, PackageRecord, RepoDataRecord + compute_package_url, match_spec::package_name_matcher::PackageNameMatcher, + package::ArchiveType, Channel, ChannelInfo, MatchSpec, Matches, PackageName, PackageRecord, + RepoDataRecord, }; use rattler_redaction::Redact; use serde::{ diff --git a/crates/rattler_solve/src/libsolv_c/mod.rs b/crates/rattler_solve/src/libsolv_c/mod.rs index f63121359..53377cd16 100644 --- a/crates/rattler_solve/src/libsolv_c/mod.rs +++ b/crates/rattler_solve/src/libsolv_c/mod.rs @@ -10,7 +10,10 @@ pub use input::cache_repodata; use input::{add_repodata_records, add_solv_file, add_virtual_packages}; pub use libc_byte_slice::LibcByteSlice; use output::get_required_packages; -use rattler_conda_types::{match_spec::package_name_matcher::PackageNameMatcher, MatchSpec, NamelessMatchSpec, RepoDataRecord, SolverResult}; +use rattler_conda_types::{ + match_spec::package_name_matcher::PackageNameMatcher, MatchSpec, NamelessMatchSpec, + RepoDataRecord, SolverResult, +}; use wrapper::{ flags::SolverFlag, pool::{Pool, Verbosity}, diff --git a/crates/rattler_solve/src/resolvo/mod.rs b/crates/rattler_solve/src/resolvo/mod.rs index c1b7b5514..7f758d257 100644 --- a/crates/rattler_solve/src/resolvo/mod.rs +++ b/crates/rattler_solve/src/resolvo/mod.rs @@ -12,10 +12,8 @@ use chrono::{DateTime, Utc}; use conda_sorting::SolvableSorter; use itertools::Itertools; use rattler_conda_types::{ - match_spec::package_name_matcher::{package_name_matcher_to_package_name, PackageNameMatcher}, - package::ArchiveType, - GenericVirtualPackage, MatchSpec, Matches, NamelessMatchSpec, PackageName, ParseMatchSpecError, - ParseStrictness, RepoDataRecord, SolverResult, + package::ArchiveType, GenericVirtualPackage, MatchSpec, Matches, NamelessMatchSpec, + PackageName, ParseMatchSpecError, ParseStrictness, RepoDataRecord, SolverResult, }; use resolvo::{ utils::{Pool, VersionSet}, @@ -299,8 +297,7 @@ impl<'a> CondaDependencyProvider<'a> { let direct_dependencies = match_specs .iter() .filter_map(|spec| spec.name.as_ref()) - .map(package_name_matcher_to_package_name) - .map(|name| pool.intern_package_name(&name)) + .map(|name| pool.intern_package_name(name.as_exact())) .collect(); // TODO: Normalize these channel names to urls so we can compare them correctly. @@ -406,10 +403,11 @@ impl<'a> CondaDependencyProvider<'a> { // Add to excluded when package is not in the specified channel. if !channel_specific_specs.is_empty() { if let Some(spec) = channel_specific_specs.iter().find(|&&spec| { - package_name_matcher_to_package_name( - spec.name.as_ref().expect("expecting a name"), - ) - .as_normalized() + spec.name + .as_ref() + .expect("expecting a name") + .as_exact() + .as_normalized() == record.package_record.name.as_normalized() }) { // Check if the spec has a channel, and compare it to the repodata @@ -849,9 +847,7 @@ impl super::SolverImpl for Solver { let (Some(name), spec) = spec.clone().into_nameless() else { unimplemented!("matchspecs without a name are not supported"); }; - let name_id = provider - .pool - .intern_package_name(&package_name_matcher_to_package_name(&name)); + let name_id = provider.pool.intern_package_name(name.as_exact()); provider.pool.intern_version_set(name_id, spec.into()) }) .collect(); @@ -930,13 +926,13 @@ fn version_sets_for_match_spec( for extra in spec.extras.iter().flatten() { version_set_ids.push(extra_version_set( pool, - package_name_matcher_to_package_name(&name), + name.clone().into_exact(), extra.clone(), )); } // Create a version set for the match spec itself. - let dependency_name = pool.intern_package_name(&package_name_matcher_to_package_name(&name)); + let dependency_name = pool.intern_package_name(name.clone().as_exact()); let version_set_id = pool.intern_version_set(dependency_name, spec.into()); version_set_ids.push(version_set_id); From d6d203452ef3cc1164fbdb1c2086cf4d3bdf0af9 Mon Sep 17 00:00:00 2001 From: Pavel Zwerschke Date: Fri, 26 Sep 2025 18:51:29 +0200 Subject: [PATCH 05/18] fix --- crates/rattler/src/install/installer/mod.rs | 2 +- crates/rattler_conda_types/src/lib.rs | 1 - .../rattler_conda_types/src/match_spec/mod.rs | 7 +++-- .../src/match_spec/package_name_matcher.rs | 24 ++------------- .../src/gateway/query.rs | 7 +++-- .../src/sparse/mod.rs | 11 +++---- crates/rattler_solve/src/resolvo/mod.rs | 10 +++---- crates/rattler_solve/tests/backends.rs | 29 +++++++------------ crates/rattler_solve/tests/sorting.rs | 5 ++-- 9 files changed, 33 insertions(+), 63 deletions(-) diff --git a/crates/rattler/src/install/installer/mod.rs b/crates/rattler/src/install/installer/mod.rs index 3038c6f41..3e40f0735 100644 --- a/crates/rattler/src/install/installer/mod.rs +++ b/crates/rattler/src/install/installer/mod.rs @@ -847,7 +847,7 @@ fn create_spec_mapping(specs: &[MatchSpec]) -> std::collections::HashMap PackageName { + pub fn unwrap_into_exact(self) -> PackageName { match self { PackageNameMatcher::Exact(s) => s, _ => panic!("PackageNameMatcher is not Exact"), @@ -70,7 +70,7 @@ impl PackageNameMatcher { } /// Convert [`PackageNameMatcher`] to [`PackageName`]. - pub fn as_exact(&self) -> &PackageName { + pub fn unwrap_as_exact(&self) -> &PackageName { match self { PackageNameMatcher::Exact(s) => s, _ => panic!("PackageNameMatcher is not Exact"), @@ -178,26 +178,6 @@ pub enum IntoPackageNameError { NotExact, } -impl TryInto for PackageNameMatcher { - type Error = IntoPackageNameError; - fn try_into(self) -> Result { - match self { - PackageNameMatcher::Exact(name) => Ok(name), - _ => Err(IntoPackageNameError::NotExact), - } - } -} - -impl TryInto for &PackageNameMatcher { - type Error = IntoPackageNameError; - fn try_into(self) -> Result { - match self { - PackageNameMatcher::Exact(name) => Ok(name.clone()), - _ => Err(IntoPackageNameError::NotExact), - } - } -} - #[cfg(test)] mod tests { use super::*; diff --git a/crates/rattler_repodata_gateway/src/gateway/query.rs b/crates/rattler_repodata_gateway/src/gateway/query.rs index ce03b6608..005db8316 100644 --- a/crates/rattler_repodata_gateway/src/gateway/query.rs +++ b/crates/rattler_repodata_gateway/src/gateway/query.rs @@ -112,18 +112,19 @@ impl RepoDataQuery { let mut seen = HashSet::new(); let mut pending_package_specs = HashMap::new(); let mut direct_url_specs = vec![]; + // TODO: allow glob/regex package names as well for spec in self.specs { if let Some(url) = spec.url.clone() { let name = spec .name .clone() .ok_or(GatewayError::MatchSpecWithoutName(Box::new(spec.clone())))?; - seen.insert(name.clone().into_exact()); + seen.insert(name.clone().unwrap_into_exact()); direct_url_specs.push((spec.clone(), url, name.clone())); } else if let Some(name) = &spec.name { - seen.insert(name.clone().into_exact()); + seen.insert(name.clone().unwrap_into_exact()); let pending = pending_package_specs - .entry(name.clone().into_exact()) + .entry(name.clone().unwrap_into_exact()) .or_insert_with(|| SourceSpecs::Input(vec![])); let SourceSpecs::Input(input_specs) = pending else { panic!("RootSpecs::Input was overwritten by RootSpecs::Transitive"); diff --git a/crates/rattler_repodata_gateway/src/sparse/mod.rs b/crates/rattler_repodata_gateway/src/sparse/mod.rs index d544db708..88b13b204 100644 --- a/crates/rattler_repodata_gateway/src/sparse/mod.rs +++ b/crates/rattler_repodata_gateway/src/sparse/mod.rs @@ -15,9 +15,8 @@ use bytes::Bytes; use fs_err as fs; use itertools::Itertools; use rattler_conda_types::{ - compute_package_url, match_spec::package_name_matcher::PackageNameMatcher, - package::ArchiveType, Channel, ChannelInfo, MatchSpec, Matches, PackageName, PackageRecord, - RepoDataRecord, + compute_package_url, package::ArchiveType, Channel, ChannelInfo, MatchSpec, Matches, + PackageName, PackageRecord, RepoDataRecord, }; use rattler_redaction::Redact; use serde::{ @@ -261,10 +260,8 @@ impl SparseRepoData { let base_url = repo_data.info.as_ref().and_then(|i| i.base_url.as_deref()); for (package_name, specs) in &spec.into_iter().chunk_by(|spec| spec.borrow().name.clone()) { let grouped_specs = specs.into_iter().collect::>(); - let package_name = match package_name { - Some(PackageNameMatcher::Exact(name)) => Some(name), - _ => None, // todo: this can lead to unexpected results in some scenarios - }; + // TODO: support glob/regex package names + let package_name = package_name.map(|name| name.unwrap_into_exact()); let mut parsed_records = parse_records( package_name.as_ref(), &repo_data.packages, diff --git a/crates/rattler_solve/src/resolvo/mod.rs b/crates/rattler_solve/src/resolvo/mod.rs index 7f758d257..98b2e1da1 100644 --- a/crates/rattler_solve/src/resolvo/mod.rs +++ b/crates/rattler_solve/src/resolvo/mod.rs @@ -297,7 +297,7 @@ impl<'a> CondaDependencyProvider<'a> { let direct_dependencies = match_specs .iter() .filter_map(|spec| spec.name.as_ref()) - .map(|name| pool.intern_package_name(name.as_exact())) + .map(|name| pool.intern_package_name(name.unwrap_as_exact())) .collect(); // TODO: Normalize these channel names to urls so we can compare them correctly. @@ -406,7 +406,7 @@ impl<'a> CondaDependencyProvider<'a> { spec.name .as_ref() .expect("expecting a name") - .as_exact() + .unwrap_as_exact() .as_normalized() == record.package_record.name.as_normalized() }) { @@ -847,7 +847,7 @@ impl super::SolverImpl for Solver { let (Some(name), spec) = spec.clone().into_nameless() else { unimplemented!("matchspecs without a name are not supported"); }; - let name_id = provider.pool.intern_package_name(name.as_exact()); + let name_id = provider.pool.intern_package_name(name.unwrap_as_exact()); provider.pool.intern_version_set(name_id, spec.into()) }) .collect(); @@ -926,13 +926,13 @@ fn version_sets_for_match_spec( for extra in spec.extras.iter().flatten() { version_set_ids.push(extra_version_set( pool, - name.clone().into_exact(), + name.clone().unwrap_into_exact(), extra.clone(), )); } // Create a version set for the match spec itself. - let dependency_name = pool.intern_package_name(name.clone().as_exact()); + let dependency_name = pool.intern_package_name(name.clone().unwrap_as_exact()); let version_set_id = pool.intern_version_set(dependency_name, spec.into()); version_set_ids.push(version_set_id); diff --git a/crates/rattler_solve/tests/backends.rs b/crates/rattler_solve/tests/backends.rs index 000b9b2eb..9c2104086 100644 --- a/crates/rattler_solve/tests/backends.rs +++ b/crates/rattler_solve/tests/backends.rs @@ -3,9 +3,8 @@ use std::{collections::BTreeMap, str::FromStr, time::Instant}; use chrono::{DateTime, Utc}; use once_cell::sync::Lazy; use rattler_conda_types::{ - match_spec::package_name_matcher::package_name_matcher_to_package_name, Channel, ChannelConfig, - GenericVirtualPackage, MatchSpec, NoArchType, PackageRecord, ParseStrictness, RepoData, - RepoDataRecord, SolverResult, Version, + Channel, ChannelConfig, GenericVirtualPackage, MatchSpec, NoArchType, PackageRecord, + ParseStrictness, RepoData, RepoDataRecord, SolverResult, Version, }; use rattler_repodata_gateway::sparse::{PackageFormatSelection, SparseRepoData}; use rattler_solve::{ChannelPriority, SolveError, SolveStrategy, SolverImpl, SolverTask}; @@ -133,11 +132,9 @@ fn solve_real_world(specs: Vec<&str>) -> Vec { let sparse_repo_data = read_real_world_repo_data(); - let names = specs.iter().filter_map(|s| { - s.name - .as_ref() - .map(|n| package_name_matcher_to_package_name(&n)) - }); + let names = specs + .iter() + .filter_map(|s| s.name.as_ref().map(|n| n.clone().unwrap_into_exact())); let available_packages = SparseRepoData::load_records_recursive( sparse_repo_data, names, @@ -1161,11 +1158,9 @@ fn compare_solve(task: CompareTask<'_>) { let sparse_repo_data = read_real_world_repo_data(); - let names = specs.iter().filter_map(|s| { - s.name - .as_ref() - .map(|n| package_name_matcher_to_package_name(&n)) - }); + let names = specs + .iter() + .filter_map(|s| s.name.as_ref().map(|n| n.clone().unwrap_into_exact())); let available_packages = SparseRepoData::load_records_recursive( sparse_repo_data, names, @@ -1301,11 +1296,9 @@ fn solve_to_get_channel_of_spec( ) { let spec = MatchSpec::from_str(spec_str, ParseStrictness::Lenient).unwrap(); let specs = vec![spec.clone()]; - let names = specs.iter().filter_map(|s| { - s.name - .as_ref() - .map(|n| package_name_matcher_to_package_name(&n)) - }); + let names = specs + .iter() + .filter_map(|s| s.name.as_ref().map(|n| n.clone().unwrap_into_exact())); let available_packages = SparseRepoData::load_records_recursive( repo_data, diff --git a/crates/rattler_solve/tests/sorting.rs b/crates/rattler_solve/tests/sorting.rs index 7868057b9..3e3b0280a 100644 --- a/crates/rattler_solve/tests/sorting.rs +++ b/crates/rattler_solve/tests/sorting.rs @@ -5,8 +5,7 @@ use std::path::Path; use futures::FutureExt; use itertools::Itertools; use rattler_conda_types::{ - match_spec::package_name_matcher::package_name_matcher_to_package_name, Channel, MatchSpec, - PackageName, ParseStrictness::Lenient, RepoDataRecord, + Channel, MatchSpec, PackageName, ParseStrictness::Lenient, RepoDataRecord, }; use rattler_repodata_gateway::sparse::{PackageFormatSelection, SparseRepoData}; use rattler_solve::{resolvo::CondaDependencyProvider, ChannelPriority, SolveStrategy}; @@ -36,7 +35,7 @@ fn load_repodata(package_name: &PackageName) -> Vec> { fn create_sorting_snapshot(package_name: &str, strategy: SolveStrategy) -> String { let match_spec = MatchSpec::from_str(package_name, Lenient).unwrap(); - let package_name = package_name_matcher_to_package_name(&match_spec.name.clone().unwrap()); + let package_name = match_spec.name.clone().unwrap().unwrap_into_exact(); // Load repodata let repodata = load_repodata(&package_name); From 3d0c307ee23ef6f69699e8ee27d4fc799dcc2b7f Mon Sep 17 00:00:00 2001 From: Pavel Zwerschke Date: Fri, 26 Sep 2025 19:27:48 +0200 Subject: [PATCH 06/18] py-rattler --- crates/rattler_conda_types/src/lib.rs | 1 + .../src/match_spec/package_name_matcher.rs | 2 +- .../src/match_spec/parse.rs | 10 +--- py-rattler/Cargo.lock | 31 ++++++----- py-rattler/src/error.rs | 10 +++- py-rattler/src/lib.rs | 9 ++- py-rattler/src/match_spec.rs | 10 ++-- py-rattler/src/package_name_matcher.rs | 55 +++++++++++++++++++ py-rattler/src/solver.rs | 2 +- 9 files changed, 97 insertions(+), 33 deletions(-) create mode 100644 py-rattler/src/package_name_matcher.rs diff --git a/crates/rattler_conda_types/src/lib.rs b/crates/rattler_conda_types/src/lib.rs index c83e1eafc..ae41ba48d 100644 --- a/crates/rattler_conda_types/src/lib.rs +++ b/crates/rattler_conda_types/src/lib.rs @@ -48,6 +48,7 @@ pub use match_spec::{ pub use minimal_prefix_record::{ collect_minimal_prefix_records, MinimalPrefixCollection, MinimalPrefixRecord, }; +pub use match_spec::package_name_matcher::{PackageNameMatcher, PackageNameMatcherParseError}; pub use no_arch_type::{NoArchKind, NoArchType, RawNoArchType}; pub use package_name::{InvalidPackageNameError, PackageName}; pub use parse_mode::ParseStrictness; diff --git a/crates/rattler_conda_types/src/match_spec/package_name_matcher.rs b/crates/rattler_conda_types/src/match_spec/package_name_matcher.rs index 6ac7e2e7d..d2bc3948d 100644 --- a/crates/rattler_conda_types/src/match_spec/package_name_matcher.rs +++ b/crates/rattler_conda_types/src/match_spec/package_name_matcher.rs @@ -189,7 +189,7 @@ mod tests { "foo".parse().unwrap() ); assert_eq!( - PackageNameMatcher::Glob(glob::Pattern::new("foo*").unwrap()), + PackageNameMatcher::Glob(glob::Pattern::new("foo*bar").unwrap()), "foo*bar".parse().unwrap() ); assert_eq!( diff --git a/crates/rattler_conda_types/src/match_spec/parse.rs b/crates/rattler_conda_types/src/match_spec/parse.rs index 41e1ab63b..e8da7aca3 100644 --- a/crates/rattler_conda_types/src/match_spec/parse.rs +++ b/crates/rattler_conda_types/src/match_spec/parse.rs @@ -30,7 +30,7 @@ use crate::{ version_tree::{recognize_constraint, recognize_version}, ParseVersionSpecError, }, - Channel, ChannelConfig, InvalidPackageNameError, NamelessMatchSpec, ParseChannelError, + Channel, ChannelConfig, NamelessMatchSpec, ParseChannelError, ParseStrictness::{self, Lenient, Strict}, ParseVersionError, Platform, VersionSpec, }; @@ -96,10 +96,6 @@ pub enum ParseMatchSpecError { #[error("unable to parse hash digest from hex")] InvalidHashDigest, - /// The package name was invalid - #[error(transparent)] - InvalidPackageName(#[from] InvalidPackageNameError), - /// The package name matcher was invalid #[error(transparent)] InvalidPackageNameMatcher(#[from] PackageNameMatcherParseError), @@ -1363,7 +1359,7 @@ mod tests { .expect_err("Should try to parse as name not url"); assert_eq!( err.to_string(), - "'bla/bla' is not a valid package name. Package names can only contain 0-9, a-z, A-Z, -, _, or ." + "invalid package name bla/bla: 'bla/bla' is not a valid package name. Package names can only contain 0-9, a-z, A-Z, -, _, or ." ); } @@ -1379,7 +1375,7 @@ mod tests { fn test_issue_717() { assert_matches!( MatchSpec::from_str("ray[default,data] >=2.9.0,<3.0.0", Strict), - Err(ParseMatchSpecError::InvalidPackageName(_)) + Err(ParseMatchSpecError::InvalidPackageNameMatcher(_)) ); } diff --git a/py-rattler/Cargo.lock b/py-rattler/Cargo.lock index 26a82b667..387574a00 100644 --- a/py-rattler/Cargo.lock +++ b/py-rattler/Cargo.lock @@ -1116,7 +1116,7 @@ checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" [[package]] name = "coalesced_map" -version = "0.1.1" +version = "0.1.2" dependencies = [ "dashmap", "tokio", @@ -3869,7 +3869,7 @@ dependencies = [ [[package]] name = "rattler" -version = "0.37.1" +version = "0.37.4" dependencies = [ "anyhow", "console", @@ -3911,7 +3911,7 @@ dependencies = [ [[package]] name = "rattler_cache" -version = "0.3.33" +version = "0.3.34" dependencies = [ "anyhow", "dashmap", @@ -3942,7 +3942,7 @@ dependencies = [ [[package]] name = "rattler_conda_types" -version = "0.39.1" +version = "0.39.2" dependencies = [ "chrono", "core-foundation 0.10.1", @@ -3955,6 +3955,7 @@ dependencies = [ "indexmap 2.10.0", "itertools 0.14.0", "lazy-regex", + "memmap2", "nom", "nom-language", "purl", @@ -3980,7 +3981,7 @@ dependencies = [ [[package]] name = "rattler_config" -version = "0.2.8" +version = "0.2.9" dependencies = [ "console", "fs-err", @@ -4012,7 +4013,7 @@ dependencies = [ [[package]] name = "rattler_index" -version = "0.25.0" +version = "0.25.2" dependencies = [ "anyhow", "bytes", @@ -4046,7 +4047,7 @@ dependencies = [ [[package]] name = "rattler_lock" -version = "0.24.0" +version = "0.24.1" dependencies = [ "chrono", "file_url", @@ -4078,7 +4079,7 @@ dependencies = [ [[package]] name = "rattler_menuinst" -version = "0.2.24" +version = "0.2.27" dependencies = [ "chrono", "configparser", @@ -4106,7 +4107,7 @@ dependencies = [ [[package]] name = "rattler_networking" -version = "0.25.12" +version = "0.25.13" dependencies = [ "anyhow", "async-once-cell", @@ -4135,7 +4136,7 @@ dependencies = [ [[package]] name = "rattler_package_streaming" -version = "0.23.3" +version = "0.23.4" dependencies = [ "bzip2", "chrono", @@ -4181,7 +4182,7 @@ dependencies = [ [[package]] name = "rattler_repodata_gateway" -version = "0.24.3" +version = "0.24.4" dependencies = [ "anyhow", "async-compression", @@ -4239,7 +4240,7 @@ dependencies = [ [[package]] name = "rattler_s3" -version = "0.1.1" +version = "0.1.2" dependencies = [ "aws-config", "aws-credential-types", @@ -4254,7 +4255,7 @@ dependencies = [ [[package]] name = "rattler_shell" -version = "0.24.10" +version = "0.25.0" dependencies = [ "anyhow", "enum_dispatch", @@ -4272,7 +4273,7 @@ dependencies = [ [[package]] name = "rattler_solve" -version = "3.0.2" +version = "3.0.3" dependencies = [ "chrono", "futures", @@ -4288,7 +4289,7 @@ dependencies = [ [[package]] name = "rattler_virtual_packages" -version = "2.1.4" +version = "2.1.5" dependencies = [ "archspec", "libloading", diff --git a/py-rattler/src/error.rs b/py-rattler/src/error.rs index 1c209f9d8..b970fc6f1 100644 --- a/py-rattler/src/error.rs +++ b/py-rattler/src/error.rs @@ -4,9 +4,7 @@ use pyo3::exceptions::PyValueError; use pyo3::{create_exception, exceptions::PyException, PyErr}; use rattler::install::TransactionError; use rattler_conda_types::{ - ConvertSubdirError, InvalidPackageNameError, ParseArchError, ParseChannelError, - ParseMatchSpecError, ParsePlatformError, ParseVersionError, ValidatePackageRecordsError, - VersionBumpError, VersionExtendError, + ConvertSubdirError, InvalidPackageNameError, PackageNameMatcherParseError, ParseArchError, ParseChannelError, ParseMatchSpecError, ParsePlatformError, ParseVersionError, ValidatePackageRecordsError, VersionBumpError, VersionExtendError }; use rattler_lock::{ConversionError, ParseCondaLockError}; use rattler_networking::authentication_storage::AuthenticationStorageError; @@ -27,6 +25,8 @@ pub enum PyRattlerError { #[error(transparent)] InvalidPackageName(#[from] InvalidPackageNameError), #[error(transparent)] + PackageNameMatcherParseError(#[from] PackageNameMatcherParseError), + #[error(transparent)] InvalidUrl(#[from] url::ParseError), #[error(transparent)] InvalidChannel(#[from] ParseChannelError), @@ -111,6 +111,9 @@ impl From for PyErr { PyRattlerError::InvalidPackageName(err) => { InvalidPackageNameException::new_err(pretty_print_error(&err)) } + PyRattlerError::PackageNameMatcherParseError(err) => { + PackageNameMatcherParseException::new_err(pretty_print_error(&err)) + } PyRattlerError::InvalidUrl(err) => { InvalidUrlException::new_err(pretty_print_error(&err)) } @@ -196,6 +199,7 @@ impl From for PyErr { create_exception!(exceptions, InvalidVersionException, PyException); create_exception!(exceptions, InvalidMatchSpecException, PyException); create_exception!(exceptions, InvalidPackageNameException, PyException); +create_exception!(exceptions, PackageNameMatcherParseException, PyException); create_exception!(exceptions, InvalidUrlException, PyException); create_exception!(exceptions, InvalidChannelException, PyException); create_exception!(exceptions, ActivationException, PyException); diff --git a/py-rattler/src/lib.rs b/py-rattler/src/lib.rs index cc653d0af..89314c194 100644 --- a/py-rattler/src/lib.rs +++ b/py-rattler/src/lib.rs @@ -12,6 +12,7 @@ mod nameless_match_spec; mod networking; mod no_arch_type; mod package_name; +mod package_name_matcher; mod package_streaming; mod paths_json; mod platform; @@ -34,7 +35,7 @@ use channel::{PyChannel, PyChannelConfig, PyChannelPriority}; use error::{ ActivationException, CacheDirException, ConvertSubdirException, DetectVirtualPackageException, EnvironmentCreationException, ExtractException, FetchRepoDataException, - InvalidChannelException, InvalidMatchSpecException, InvalidPackageNameException, + InvalidChannelException, InvalidMatchSpecException, InvalidPackageNameException, PackageNameMatcherParseException, InvalidUrlException, InvalidVersionException, IoException, LinkException, ParseArchException, ParsePlatformException, PyRattlerError, SolverException, TransactionException, ValidatePackageRecordsException, VersionBumpException, @@ -58,6 +59,7 @@ use networking::middleware::{ use networking::{client::PyClientWithMiddleware, py_fetch_repo_data}; use no_arch_type::PyNoArchType; use package_name::PyPackageName; +use package_name_matcher::PyPackageNameMatcher; use paths_json::{PyFileMode, PyPathType, PyPathsEntry, PyPathsJson, PyPrefixPlaceholder}; use platform::{PyArch, PyPlatform}; use prefix_paths::{PyPrefixPathType, PyPrefixPaths, PyPrefixPathsEntry}; @@ -97,6 +99,7 @@ fn rattler<'py>(py: Python<'py>, m: Bound<'py, PyModule>) -> PyResult<()> { m.add_class::()?; m.add_class::()?; + m.add_class::()?; m.add_class::()?; m.add_class::()?; @@ -182,6 +185,10 @@ fn rattler<'py>(py: Python<'py>, m: Bound<'py, PyModule>) -> PyResult<()> { "InvalidMatchSpecError", py.get_type::(), )?; + m.add( + "PackageNameMatcherParseError", + py.get_type::(), + )?; m.add( "InvalidPackageNameError", py.get_type::(), diff --git a/py-rattler/src/match_spec.rs b/py-rattler/src/match_spec.rs index 8e669c7d5..0be8335e8 100644 --- a/py-rattler/src/match_spec.rs +++ b/py-rattler/src/match_spec.rs @@ -1,12 +1,12 @@ -use std::borrow::Borrow; +use std::{borrow::Borrow, str::FromStr}; use std::sync::Arc; use pyo3::{pyclass, pymethods, types::PyBytes, Bound, PyResult, Python}; -use rattler_conda_types::{Channel, MatchSpec, Matches, PackageName, ParseStrictness}; +use rattler_conda_types::{Channel, MatchSpec, Matches, PackageNameMatcher, ParseStrictness}; use crate::{ channel::PyChannel, error::PyRattlerError, nameless_match_spec::PyNamelessMatchSpec, - package_name::PyPackageName, record::PyRecord, + package_name_matcher::PyPackageNameMatcher, record::PyRecord, }; #[pyclass] @@ -52,7 +52,7 @@ impl PyMatchSpec { /// The name of the package #[getter] - pub fn name(&self) -> Option { + pub fn name(&self) -> Option { self.inner.name.clone().map(std::convert::Into::into) } @@ -135,7 +135,7 @@ impl PyMatchSpec { Ok(Self { inner: MatchSpec::from_nameless( spec.clone().into(), - Some(PackageName::try_from(name).map_err(PyRattlerError::from)?), + Some(PackageNameMatcher::from_str(&name).map_err(PyRattlerError::from)?), ), }) } diff --git a/py-rattler/src/package_name_matcher.rs b/py-rattler/src/package_name_matcher.rs new file mode 100644 index 000000000..96e9d6ef2 --- /dev/null +++ b/py-rattler/src/package_name_matcher.rs @@ -0,0 +1,55 @@ +use std::{ + collections::hash_map::DefaultHasher, + hash::{Hash, Hasher}, str::FromStr, +}; + +use pyo3::{pyclass, pymethods}; +use rattler_conda_types::{PackageName, PackageNameMatcher}; + +use crate::error::PyRattlerError; + +#[pyclass] +#[repr(transparent)] +#[derive(Clone)] +pub struct PyPackageNameMatcher { + pub(crate) inner: PackageNameMatcher, +} + +impl From for PackageNameMatcher { + fn from(value: PyPackageNameMatcher) -> Self { + value.inner + } +} + +impl From for PyPackageNameMatcher { + fn from(value: PackageNameMatcher) -> Self { + Self { inner: value } + } +} + +#[pymethods] +impl PyPackageNameMatcher { + /// Constructs a new `PackageNameMatcher` from a string, checking if the string is actually a + /// valid or normalized conda package name. + #[new] + pub fn new(source: String) -> pyo3::PyResult { + Ok(PackageNameMatcher::from_str(&source) + .map(Into::into) + .map_err(PyRattlerError::from)?) + } + + /// Constructs a new exact `PackageNameMatcher` from a string without checking if the string is actually a + /// valid or normalized conda package name. This should only be used if you are sure that the + /// input string is valid. + #[staticmethod] + pub fn new_unchecked(normalized: String) -> Self { + PackageNameMatcher::Exact(PackageName::new_unchecked(normalized)).into() + } + + /// Compute the hash of the name. + fn __hash__(&self) -> u64 { + let mut hasher = DefaultHasher::new(); + self.inner.hash(&mut hasher); + hasher.finish() + } +} diff --git a/py-rattler/src/solver.rs b/py-rattler/src/solver.rs index 2b2ea2931..8819d4b38 100644 --- a/py-rattler/src/solver.rs +++ b/py-rattler/src/solver.rs @@ -158,7 +158,7 @@ pub fn py_solve_with_sparse_repodata<'py>( let package_names = specs .iter() - .filter_map(|match_spec| match_spec.inner.name.clone()); + .filter_map(|match_spec| match_spec.inner.name.as_ref().map(|n| n.clone().unwrap_into_exact())); let available_packages = SparseRepoData::load_records_recursive( repo_data_refs, From f3012c951a4d4af9a168d89c1429aaf8b4ee5768 Mon Sep 17 00:00:00 2001 From: Pavel Zwerschke Date: Fri, 10 Oct 2025 10:56:38 +0200 Subject: [PATCH 07/18] WIP --- crates/rattler/src/install/installer/mod.rs | 10 +++---- .../src/match_spec/package_name_matcher.rs | 19 ++++-------- crates/rattler_solve/src/resolvo/mod.rs | 30 +++++++++---------- 3 files changed, 26 insertions(+), 33 deletions(-) diff --git a/crates/rattler/src/install/installer/mod.rs b/crates/rattler/src/install/installer/mod.rs index 60b25ee17..600bdc499 100644 --- a/crates/rattler/src/install/installer/mod.rs +++ b/crates/rattler/src/install/installer/mod.rs @@ -35,7 +35,7 @@ use itertools::Itertools; use rattler_cache::package_cache::{CacheLock, CacheReporter}; use rattler_conda_types::{ prefix_record::{Link, LinkType}, - MatchSpec, PackageName, Platform, PrefixRecord, RepoDataRecord, + MatchSpec, PackageName, PackageNameMatcher, Platform, PrefixRecord, RepoDataRecord, }; use rattler_networking::retry_policies::default_retry_policy; use rattler_networking::LazyClient; @@ -830,15 +830,15 @@ fn update_requested_specs_in_json( /// - The value is a vector of string representations of all matching /// `MatchSpecs` /// -/// `MatchSpecs` without names are skipped. -/// For multiple `MatchSpecs` with the same package name, all are collected. +/// Only `MatchSpec`s that have a `PackageNameMatcher::Exact` are included. +/// For multiple `MatchSpec`s with the same package name, all are collected. fn create_spec_mapping(specs: &[MatchSpec]) -> std::collections::HashMap> { let mut mapping = std::collections::HashMap::new(); for spec in specs { - if let Some(name) = &spec.name { + if let Some(PackageNameMatcher::Exact(name)) = &spec.name { mapping - .entry(name.unwrap_as_exact().clone()) + .entry(name.clone()) .or_insert_with(Vec::new) .push(spec.to_string()); } diff --git a/crates/rattler_conda_types/src/match_spec/package_name_matcher.rs b/crates/rattler_conda_types/src/match_spec/package_name_matcher.rs index d2bc3948d..ad48ede3a 100644 --- a/crates/rattler_conda_types/src/match_spec/package_name_matcher.rs +++ b/crates/rattler_conda_types/src/match_spec/package_name_matcher.rs @@ -60,20 +60,13 @@ impl PackageNameMatcher { PackageNameMatcher::Regex(regex) => regex.is_match(other.as_normalized()), } } +} - /// Convert [`PackageNameMatcher`] to [`PackageName`]. - pub fn unwrap_into_exact(self) -> PackageName { - match self { - PackageNameMatcher::Exact(s) => s, - _ => panic!("PackageNameMatcher is not Exact"), - } - } - - /// Convert [`PackageNameMatcher`] to [`PackageName`]. - pub fn unwrap_as_exact(&self) -> &PackageName { - match self { - PackageNameMatcher::Exact(s) => s, - _ => panic!("PackageNameMatcher is not Exact"), +impl From for Option { + fn from(value: PackageNameMatcher) -> Self { + match value { + PackageNameMatcher::Exact(s) => Some(s), + _ => None, } } } diff --git a/crates/rattler_solve/src/resolvo/mod.rs b/crates/rattler_solve/src/resolvo/mod.rs index 98b2e1da1..36bade7d9 100644 --- a/crates/rattler_solve/src/resolvo/mod.rs +++ b/crates/rattler_solve/src/resolvo/mod.rs @@ -13,7 +13,8 @@ use conda_sorting::SolvableSorter; use itertools::Itertools; use rattler_conda_types::{ package::ArchiveType, GenericVirtualPackage, MatchSpec, Matches, NamelessMatchSpec, - PackageName, ParseMatchSpecError, ParseStrictness, RepoDataRecord, SolverResult, + PackageName, PackageNameMatcher, ParseMatchSpecError, ParseStrictness, RepoDataRecord, + SolverResult, }; use resolvo::{ utils::{Pool, VersionSet}, @@ -297,7 +298,8 @@ impl<'a> CondaDependencyProvider<'a> { let direct_dependencies = match_specs .iter() .filter_map(|spec| spec.name.as_ref()) - .map(|name| pool.intern_package_name(name.unwrap_as_exact())) + .filter_map(|name| Option::::from(name.clone())) + .map(|name| pool.intern_package_name(&name)) .collect(); // TODO: Normalize these channel names to urls so we can compare them correctly. @@ -405,8 +407,9 @@ impl<'a> CondaDependencyProvider<'a> { if let Some(spec) = channel_specific_specs.iter().find(|&&spec| { spec.name .as_ref() - .expect("expecting a name") - .unwrap_as_exact() + .map(|name| Option::::from(name.clone())) + .flatten() + .expect("expecting an exact package name") .as_normalized() == record.package_record.name.as_normalized() }) { @@ -844,10 +847,11 @@ impl super::SolverImpl for Solver { .constraints .iter() .map(|spec| { - let (Some(name), spec) = spec.clone().into_nameless() else { - unimplemented!("matchspecs without a name are not supported"); + let (Some(PackageNameMatcher::Exact(name)), spec) = spec.clone().into_nameless() + else { + unimplemented!("only exact package names are supported"); }; - let name_id = provider.pool.intern_package_name(name.unwrap_as_exact()); + let name_id = provider.pool.intern_package_name(&name); provider.pool.intern_version_set(name_id, spec.into()) }) .collect(); @@ -917,22 +921,18 @@ fn version_sets_for_match_spec( pool: &Pool, NameType>, spec: MatchSpec, ) -> Vec { - let (Some(name), spec) = spec.into_nameless() else { - unimplemented!("matchspecs without a name are not supported"); + let (Some(PackageNameMatcher::Exact(name)), spec) = spec.into_nameless() else { + unimplemented!("only exact package names are supported"); }; // Add a dependency on each extra. let mut version_set_ids = vec![]; for extra in spec.extras.iter().flatten() { - version_set_ids.push(extra_version_set( - pool, - name.clone().unwrap_into_exact(), - extra.clone(), - )); + version_set_ids.push(extra_version_set(pool, name.clone(), extra.clone())); } // Create a version set for the match spec itself. - let dependency_name = pool.intern_package_name(name.clone().unwrap_as_exact()); + let dependency_name = pool.intern_package_name(&name); let version_set_id = pool.intern_version_set(dependency_name, spec.into()); version_set_ids.push(version_set_id); From 3360f7096785336c33989eb60fb17e6455d2d7b0 Mon Sep 17 00:00:00 2001 From: Pavel Zwerschke Date: Fri, 10 Oct 2025 11:30:10 +0200 Subject: [PATCH 08/18] fix --- .../src/gateway/error.rs | 4 +-- .../src/gateway/mod.rs | 2 +- .../src/gateway/query.rs | 20 +++++++----- .../src/sparse/mod.rs | 6 ++-- crates/rattler_solve/tests/backends.rs | 31 ++++++++++++------- crates/rattler_solve/tests/sorting.rs | 2 +- py-rattler/src/solver.rs | 2 +- 7 files changed, 41 insertions(+), 26 deletions(-) diff --git a/crates/rattler_repodata_gateway/src/gateway/error.rs b/crates/rattler_repodata_gateway/src/gateway/error.rs index 6e7deaf29..6b9fedbac 100644 --- a/crates/rattler_repodata_gateway/src/gateway/error.rs +++ b/crates/rattler_repodata_gateway/src/gateway/error.rs @@ -46,8 +46,8 @@ pub enum GatewayError { #[source] super::direct_url_query::DirectUrlQueryError, ), - #[error("the match spec '{0}' does not specify a name")] - MatchSpecWithoutName(Box), + #[error("the match spec '{0}' does not specify an exact name")] + MatchSpecWithoutExactName(Box), #[error("the package from url '{0}', doesn't have the same name as the match spec filename intents '{1}'")] UrlRecordNameMismatch(String, String), diff --git a/crates/rattler_repodata_gateway/src/gateway/mod.rs b/crates/rattler_repodata_gateway/src/gateway/mod.rs index 6f8d007bc..4853f475e 100644 --- a/crates/rattler_repodata_gateway/src/gateway/mod.rs +++ b/crates/rattler_repodata_gateway/src/gateway/mod.rs @@ -577,7 +577,7 @@ mod test { .await .unwrap_err(); - assert_matches!(gateway_error, GatewayError::MatchSpecWithoutName(_)); + assert_matches!(gateway_error, GatewayError::MatchSpecWithoutExactName(_)); } #[rstest] diff --git a/crates/rattler_repodata_gateway/src/gateway/query.rs b/crates/rattler_repodata_gateway/src/gateway/query.rs index 005db8316..7374dab69 100644 --- a/crates/rattler_repodata_gateway/src/gateway/query.rs +++ b/crates/rattler_repodata_gateway/src/gateway/query.rs @@ -6,7 +6,7 @@ use std::{ use futures::{select_biased, stream::FuturesUnordered, FutureExt, StreamExt}; use itertools::Itertools; -use rattler_conda_types::{Channel, MatchSpec, Matches, PackageName, Platform}; +use rattler_conda_types::{Channel, MatchSpec, Matches, PackageName, PackageNameMatcher, Platform}; use super::{subdir::Subdir, BarrierCell, GatewayError, GatewayInner, RepoData}; use crate::Reporter; @@ -118,13 +118,17 @@ impl RepoDataQuery { let name = spec .name .clone() - .ok_or(GatewayError::MatchSpecWithoutName(Box::new(spec.clone())))?; - seen.insert(name.clone().unwrap_into_exact()); + .map(Option::::from) + .flatten() + .ok_or(GatewayError::MatchSpecWithoutExactName(Box::new( + spec.clone(), + )))?; + seen.insert(name.clone()); direct_url_specs.push((spec.clone(), url, name.clone())); - } else if let Some(name) = &spec.name { - seen.insert(name.clone().unwrap_into_exact()); + } else if let Some(PackageNameMatcher::Exact(name)) = &spec.name { + seen.insert(name.clone()); let pending = pending_package_specs - .entry(name.clone().unwrap_into_exact()) + .entry(name.clone()) .or_insert_with(|| SourceSpecs::Input(vec![])); let SourceSpecs::Input(input_specs) = pending else { panic!("RootSpecs::Input was overwritten by RootSpecs::Transitive"); @@ -198,11 +202,11 @@ impl RepoDataQuery { // Check if record actually has the same name if let Some(record) = record.first() { - if !name.matches(&record.package_record.name) { + if record.package_record.name != name { // Using as_source to get the closest to the retrieved input. return Err(GatewayError::UrlRecordNameMismatch( record.package_record.name.as_source().to_string(), - name.to_string(), + name.as_source().to_string(), )); } } diff --git a/crates/rattler_repodata_gateway/src/sparse/mod.rs b/crates/rattler_repodata_gateway/src/sparse/mod.rs index 88b13b204..2622bf22a 100644 --- a/crates/rattler_repodata_gateway/src/sparse/mod.rs +++ b/crates/rattler_repodata_gateway/src/sparse/mod.rs @@ -261,9 +261,11 @@ impl SparseRepoData { for (package_name, specs) in &spec.into_iter().chunk_by(|spec| spec.borrow().name.clone()) { let grouped_specs = specs.into_iter().collect::>(); // TODO: support glob/regex package names - let package_name = package_name.map(|name| name.unwrap_into_exact()); let mut parsed_records = parse_records( - package_name.as_ref(), + package_name + .map(Option::::from) + .flatten() + .as_ref(), &repo_data.packages, &repo_data.conda_packages, variant_consolidation, diff --git a/crates/rattler_solve/tests/backends.rs b/crates/rattler_solve/tests/backends.rs index 9c2104086..2a1549f86 100644 --- a/crates/rattler_solve/tests/backends.rs +++ b/crates/rattler_solve/tests/backends.rs @@ -3,8 +3,8 @@ use std::{collections::BTreeMap, str::FromStr, time::Instant}; use chrono::{DateTime, Utc}; use once_cell::sync::Lazy; use rattler_conda_types::{ - Channel, ChannelConfig, GenericVirtualPackage, MatchSpec, NoArchType, PackageRecord, - ParseStrictness, RepoData, RepoDataRecord, SolverResult, Version, + Channel, ChannelConfig, GenericVirtualPackage, MatchSpec, NoArchType, PackageName, + PackageRecord, ParseStrictness, RepoData, RepoDataRecord, SolverResult, Version, }; use rattler_repodata_gateway::sparse::{PackageFormatSelection, SparseRepoData}; use rattler_solve::{ChannelPriority, SolveError, SolveStrategy, SolverImpl, SolverTask}; @@ -132,9 +132,12 @@ fn solve_real_world(specs: Vec<&str>) -> Vec { let sparse_repo_data = read_real_world_repo_data(); - let names = specs - .iter() - .filter_map(|s| s.name.as_ref().map(|n| n.clone().unwrap_into_exact())); + let names = specs.iter().filter_map(|s| { + s.name + .as_ref() + .map(|n| Option::::from(n.clone())) + .flatten() + }); let available_packages = SparseRepoData::load_records_recursive( sparse_repo_data, names, @@ -1158,9 +1161,12 @@ fn compare_solve(task: CompareTask<'_>) { let sparse_repo_data = read_real_world_repo_data(); - let names = specs - .iter() - .filter_map(|s| s.name.as_ref().map(|n| n.clone().unwrap_into_exact())); + let names = specs.iter().filter_map(|s| { + s.name + .as_ref() + .map(|n| Option::::from(n.clone())) + .flatten() + }); let available_packages = SparseRepoData::load_records_recursive( sparse_repo_data, names, @@ -1296,9 +1302,12 @@ fn solve_to_get_channel_of_spec( ) { let spec = MatchSpec::from_str(spec_str, ParseStrictness::Lenient).unwrap(); let specs = vec![spec.clone()]; - let names = specs - .iter() - .filter_map(|s| s.name.as_ref().map(|n| n.clone().unwrap_into_exact())); + let names = specs.iter().filter_map(|s| { + s.name + .as_ref() + .map(|n| Option::::from(n.clone())) + .flatten() + }); let available_packages = SparseRepoData::load_records_recursive( repo_data, diff --git a/crates/rattler_solve/tests/sorting.rs b/crates/rattler_solve/tests/sorting.rs index 3e3b0280a..bdea22d98 100644 --- a/crates/rattler_solve/tests/sorting.rs +++ b/crates/rattler_solve/tests/sorting.rs @@ -35,7 +35,7 @@ fn load_repodata(package_name: &PackageName) -> Vec> { fn create_sorting_snapshot(package_name: &str, strategy: SolveStrategy) -> String { let match_spec = MatchSpec::from_str(package_name, Lenient).unwrap(); - let package_name = match_spec.name.clone().unwrap().unwrap_into_exact(); + let package_name = Option::::from(match_spec.name.clone().unwrap()).unwrap(); // Load repodata let repodata = load_repodata(&package_name); diff --git a/py-rattler/src/solver.rs b/py-rattler/src/solver.rs index 8819d4b38..9c749e9a2 100644 --- a/py-rattler/src/solver.rs +++ b/py-rattler/src/solver.rs @@ -158,7 +158,7 @@ pub fn py_solve_with_sparse_repodata<'py>( let package_names = specs .iter() - .filter_map(|match_spec| match_spec.inner.name.as_ref().map(|n| n.clone().unwrap_into_exact())); + .filter_map(|match_spec| match_spec.inner.name.as_ref().map(|n| Option::::from(n.clone()).flatten())); let available_packages = SparseRepoData::load_records_recursive( repo_data_refs, From fe1585eeb4a33a19b0eaa145ad9bf4be505c8a46 Mon Sep 17 00:00:00 2001 From: Pavel Zwerschke Date: Fri, 10 Oct 2025 11:31:35 +0200 Subject: [PATCH 09/18] fmt --- crates/rattler_conda_types/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/rattler_conda_types/src/lib.rs b/crates/rattler_conda_types/src/lib.rs index 5f7eec531..cdb26bf3c 100644 --- a/crates/rattler_conda_types/src/lib.rs +++ b/crates/rattler_conda_types/src/lib.rs @@ -41,6 +41,7 @@ pub use explicit_environment_spec::{ ParseExplicitEnvironmentSpecError, ParsePackageArchiveHashError, }; pub use generic_virtual_package::GenericVirtualPackage; +pub use match_spec::package_name_matcher::{PackageNameMatcher, PackageNameMatcherParseError}; pub use match_spec::{ matcher::{StringMatcher, StringMatcherParseError}, parse::ParseMatchSpecError, @@ -49,7 +50,6 @@ pub use match_spec::{ pub use minimal_prefix_record::{ collect_minimal_prefix_records, MinimalPrefixCollection, MinimalPrefixRecord, }; -pub use match_spec::package_name_matcher::{PackageNameMatcher, PackageNameMatcherParseError}; pub use no_arch_type::{NoArchKind, NoArchType, RawNoArchType}; pub use package_name::{InvalidPackageNameError, PackageName}; pub use parse_mode::ParseStrictness; From 26194ae929051d03294b58076ab6eb02ce1c99bc Mon Sep 17 00:00:00 2001 From: Pavel Zwerschke Date: Fri, 10 Oct 2025 11:33:51 +0200 Subject: [PATCH 10/18] fix --- crates/rattler_repodata_gateway/src/gateway/query.rs | 3 +-- crates/rattler_repodata_gateway/src/sparse/mod.rs | 5 +---- crates/rattler_solve/src/resolvo/mod.rs | 3 +-- .../tests/snapshots/backends__resolvo__issue_717.snap.new | 8 ++++++++ 4 files changed, 11 insertions(+), 8 deletions(-) create mode 100644 crates/rattler_solve/tests/snapshots/backends__resolvo__issue_717.snap.new diff --git a/crates/rattler_repodata_gateway/src/gateway/query.rs b/crates/rattler_repodata_gateway/src/gateway/query.rs index 7374dab69..a468ab176 100644 --- a/crates/rattler_repodata_gateway/src/gateway/query.rs +++ b/crates/rattler_repodata_gateway/src/gateway/query.rs @@ -118,8 +118,7 @@ impl RepoDataQuery { let name = spec .name .clone() - .map(Option::::from) - .flatten() + .and_then(Option::::from) .ok_or(GatewayError::MatchSpecWithoutExactName(Box::new( spec.clone(), )))?; diff --git a/crates/rattler_repodata_gateway/src/sparse/mod.rs b/crates/rattler_repodata_gateway/src/sparse/mod.rs index 2622bf22a..e4b2ea044 100644 --- a/crates/rattler_repodata_gateway/src/sparse/mod.rs +++ b/crates/rattler_repodata_gateway/src/sparse/mod.rs @@ -262,10 +262,7 @@ impl SparseRepoData { let grouped_specs = specs.into_iter().collect::>(); // TODO: support glob/regex package names let mut parsed_records = parse_records( - package_name - .map(Option::::from) - .flatten() - .as_ref(), + package_name.and_then(Option::::from).as_ref(), &repo_data.packages, &repo_data.conda_packages, variant_consolidation, diff --git a/crates/rattler_solve/src/resolvo/mod.rs b/crates/rattler_solve/src/resolvo/mod.rs index 36bade7d9..fcce68f0f 100644 --- a/crates/rattler_solve/src/resolvo/mod.rs +++ b/crates/rattler_solve/src/resolvo/mod.rs @@ -407,8 +407,7 @@ impl<'a> CondaDependencyProvider<'a> { if let Some(spec) = channel_specific_specs.iter().find(|&&spec| { spec.name .as_ref() - .map(|name| Option::::from(name.clone())) - .flatten() + .and_then(|name| Option::::from(name.clone())) .expect("expecting an exact package name") .as_normalized() == record.package_record.name.as_normalized() diff --git a/crates/rattler_solve/tests/snapshots/backends__resolvo__issue_717.snap.new b/crates/rattler_solve/tests/snapshots/backends__resolvo__issue_717.snap.new new file mode 100644 index 000000000..91573419f --- /dev/null +++ b/crates/rattler_solve/tests/snapshots/backends__resolvo__issue_717.snap.new @@ -0,0 +1,8 @@ +--- +source: crates/rattler_solve/tests/backends.rs +assertion_line: 774 +expression: result.unwrap_err() +--- +Cannot solve the request because of: The following packages are incompatible +└─ issue_717 * cannot be installed because there are no viable options: + └─ issue_717 2.1 is excluded because the constrains 'ray[default,data] >=2.9.0,<3.0.0' failed to parse: invalid package name ray[default,data]: 'ray[default,data]' is not a valid package name. Package names can only contain 0-9, a-z, A-Z, -, _, or . From d712292f817469ace4f56055dbd2b58b816bab6f Mon Sep 17 00:00:00 2001 From: Pavel Zwerschke Date: Fri, 10 Oct 2025 11:42:09 +0200 Subject: [PATCH 11/18] fix --- py-rattler/Cargo.lock | 432 +++++++++++++-------------------------- py-rattler/src/solver.rs | 11 +- 2 files changed, 154 insertions(+), 289 deletions(-) diff --git a/py-rattler/Cargo.lock b/py-rattler/Cargo.lock index 387574a00..dd4d7ae56 100644 --- a/py-rattler/Cargo.lock +++ b/py-rattler/Cargo.lock @@ -343,9 +343,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "aws-config" -version = "1.5.18" +version = "1.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90aff65e86db5fe300752551c1b015ef72b708ac54bded8ef43d0d53cb7cb0b1" +checksum = "37cf2b6af2a95a20e266782b4f76f1a5e12bf412a9db2de9c1e9123b9d8c0ad8" dependencies = [ "aws-credential-types", "aws-runtime", @@ -353,7 +353,7 @@ dependencies = [ "aws-sdk-ssooidc", "aws-sdk-sts", "aws-smithy-async", - "aws-smithy-http 0.61.1", + "aws-smithy-http", "aws-smithy-json", "aws-smithy-runtime", "aws-smithy-runtime-api", @@ -362,7 +362,7 @@ dependencies = [ "bytes", "fastrand", "hex", - "http 0.2.12", + "http 1.3.1", "ring", "time", "tokio", @@ -373,9 +373,9 @@ dependencies = [ [[package]] name = "aws-credential-types" -version = "1.2.6" +version = "1.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d025db5d9f52cbc413b167136afb3d8aeea708c0d8884783cf6253be5e22f6f2" +checksum = "faf26925f4a5b59eb76722b63c2892b1d70d06fa053c72e4a100ec308c1d47bc" dependencies = [ "aws-smithy-async", "aws-smithy-runtime-api", @@ -385,15 +385,15 @@ dependencies = [ [[package]] name = "aws-runtime" -version = "1.5.9" +version = "1.5.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2090e664216c78e766b6bac10fe74d2f451c02441d43484cd76ac9a295075f7" +checksum = "bfa006bb32360ed90ac51203feafb9d02e3d21046e1fd3a450a404b90ea73e5d" dependencies = [ "aws-credential-types", "aws-sigv4", "aws-smithy-async", "aws-smithy-eventstream", - "aws-smithy-http 0.62.2", + "aws-smithy-http", "aws-smithy-runtime", "aws-smithy-runtime-api", "aws-smithy-types", @@ -410,9 +410,9 @@ dependencies = [ [[package]] name = "aws-sdk-s3" -version = "1.98.0" +version = "1.108.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "029e89cae7e628553643aecb3a3f054a0a0912ff0fd1f5d6a0b4fda421dce64b" +checksum = "200be4aed61e3c0669f7268bacb768f283f1c32a7014ce57225e1160be2f6ccb" dependencies = [ "aws-credential-types", "aws-runtime", @@ -420,7 +420,7 @@ dependencies = [ "aws-smithy-async", "aws-smithy-checksums", "aws-smithy-eventstream", - "aws-smithy-http 0.62.2", + "aws-smithy-http", "aws-smithy-json", "aws-smithy-runtime", "aws-smithy-runtime-api", @@ -444,14 +444,14 @@ dependencies = [ [[package]] name = "aws-sdk-sso" -version = "1.76.0" +version = "1.86.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64bf26698dd6d238ef1486bdda46f22a589dc813368ba868dc3d94c8d27b56ba" +checksum = "4a0abbfab841446cce6e87af853a3ba2cc1bc9afcd3f3550dd556c43d434c86d" dependencies = [ "aws-credential-types", "aws-runtime", "aws-smithy-async", - "aws-smithy-http 0.62.2", + "aws-smithy-http", "aws-smithy-json", "aws-smithy-runtime", "aws-smithy-runtime-api", @@ -466,14 +466,14 @@ dependencies = [ [[package]] name = "aws-sdk-ssooidc" -version = "1.77.0" +version = "1.88.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09cd07ed1edd939fae854a22054299ae3576500f4e0fadc560ca44f9c6ea1664" +checksum = "9a68d675582afea0e94d38b6ca9c5aaae4ca14f1d36faa6edb19b42e687e70d7" dependencies = [ "aws-credential-types", "aws-runtime", "aws-smithy-async", - "aws-smithy-http 0.62.2", + "aws-smithy-http", "aws-smithy-json", "aws-smithy-runtime", "aws-smithy-runtime-api", @@ -488,14 +488,14 @@ dependencies = [ [[package]] name = "aws-sdk-sts" -version = "1.78.0" +version = "1.88.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37f7766d2344f56d10d12f3c32993da36d78217f32594fe4fb8e57a538c1cdea" +checksum = "d30990923f4f675523c51eb1c0dec9b752fb267b36a61e83cbc219c9d86da715" dependencies = [ "aws-credential-types", "aws-runtime", "aws-smithy-async", - "aws-smithy-http 0.62.2", + "aws-smithy-http", "aws-smithy-json", "aws-smithy-query", "aws-smithy-runtime", @@ -511,13 +511,13 @@ dependencies = [ [[package]] name = "aws-sigv4" -version = "1.3.3" +version = "1.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddfb9021f581b71870a17eac25b52335b82211cdc092e02b6876b2bcefa61666" +checksum = "bffc03068fbb9c8dd5ce1c6fb240678a5cffb86fb2b7b1985c999c4b83c8df68" dependencies = [ "aws-credential-types", "aws-smithy-eventstream", - "aws-smithy-http 0.62.2", + "aws-smithy-http", "aws-smithy-runtime-api", "aws-smithy-types", "bytes", @@ -539,9 +539,9 @@ dependencies = [ [[package]] name = "aws-smithy-async" -version = "1.2.5" +version = "1.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e190749ea56f8c42bf15dd76c65e14f8f765233e6df9b0506d9d934ebef867c" +checksum = "127fcfad33b7dfc531141fda7e1c402ac65f88aca5511a4d31e2e3d2cd01ce9c" dependencies = [ "futures-util", "pin-project-lite", @@ -550,11 +550,11 @@ dependencies = [ [[package]] name = "aws-smithy-checksums" -version = "0.63.5" +version = "0.63.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ab9472f7a8ec259ddb5681d2ef1cb1cf16c0411890063e67cdc7b62562cc496" +checksum = "165d8583d8d906e2fb5511d29201d447cc710864f075debcdd9c31c265412806" dependencies = [ - "aws-smithy-http 0.62.2", + "aws-smithy-http", "aws-smithy-types", "bytes", "crc-fast", @@ -570,9 +570,9 @@ dependencies = [ [[package]] name = "aws-smithy-eventstream" -version = "0.60.10" +version = "0.60.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "604c7aec361252b8f1c871a7641d5e0ba3a7f5a586e51b66bc9510a5519594d9" +checksum = "9656b85088f8d9dc7ad40f9a6c7228e1e8447cdf4b046c87e152e0805dea02fa" dependencies = [ "aws-smithy-types", "bytes", @@ -581,29 +581,9 @@ dependencies = [ [[package]] name = "aws-smithy-http" -version = "0.61.1" +version = "0.62.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6f276f21c7921fe902826618d1423ae5bf74cf8c1b8472aee8434f3dfd31824" -dependencies = [ - "aws-smithy-runtime-api", - "aws-smithy-types", - "bytes", - "bytes-utils", - "futures-core", - "http 0.2.12", - "http-body 0.4.6", - "once_cell", - "percent-encoding", - "pin-project-lite", - "pin-utils", - "tracing", -] - -[[package]] -name = "aws-smithy-http" -version = "0.62.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43c82ba4cab184ea61f6edaafc1072aad3c2a17dcf4c0fce19ac5694b90d8b5f" +checksum = "3feafd437c763db26aa04e0cc7591185d0961e64c61885bece0fb9d50ceac671" dependencies = [ "aws-smithy-eventstream", "aws-smithy-runtime-api", @@ -622,48 +602,51 @@ dependencies = [ [[package]] name = "aws-smithy-http-client" -version = "1.0.6" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f108f1ca850f3feef3009bdcc977be201bca9a91058864d9de0684e64514bee0" +checksum = "1053b5e587e6fa40ce5a79ea27957b04ba660baa02b28b7436f64850152234f1" dependencies = [ "aws-smithy-async", "aws-smithy-runtime-api", "aws-smithy-types", - "h2 0.3.27", - "h2 0.4.11", - "http 0.2.12", - "http-body 0.4.6", - "hyper 0.14.32", - "hyper-rustls 0.24.2", + "h2", + "http 1.3.1", + "hyper", + "hyper-rustls", + "hyper-util", "pin-project-lite", - "rustls 0.21.12", + "rustls", + "rustls-native-certs", + "rustls-pki-types", "tokio", + "tokio-rustls", + "tower", "tracing", ] [[package]] name = "aws-smithy-json" -version = "0.61.4" +version = "0.61.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a16e040799d29c17412943bdbf488fd75db04112d0c0d4b9290bacf5ae0014b9" +checksum = "cff418fc8ec5cadf8173b10125f05c2e7e1d46771406187b2c878557d4503390" dependencies = [ "aws-smithy-types", ] [[package]] name = "aws-smithy-observability" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9364d5989ac4dd918e5cc4c4bdcc61c9be17dcd2586ea7f69e348fc7c6cab393" +checksum = "2d1881b1ea6d313f9890710d65c158bdab6fb08c91ea825f74c1c8c357baf4cc" dependencies = [ "aws-smithy-runtime-api", ] [[package]] name = "aws-smithy-query" -version = "0.60.7" +version = "0.60.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2fbd61ceb3fe8a1cb7352e42689cec5335833cd9f94103a61e98f9bb61c64bb" +checksum = "d28a63441360c477465f80c7abac3b9c4d075ca638f982e605b7dc2a2c7156c9" dependencies = [ "aws-smithy-types", "urlencoding", @@ -671,13 +654,12 @@ dependencies = [ [[package]] name = "aws-smithy-runtime" -version = "1.8.4" +version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3aaec682eb189e43c8a19c3dab2fe54590ad5f2cc2d26ab27608a20f2acf81c" +checksum = "40ab99739082da5347660c556689256438defae3bcefd66c52b095905730e404" dependencies = [ "aws-smithy-async", - "aws-smithy-http 0.62.2", - "aws-smithy-http-client", + "aws-smithy-http", "aws-smithy-observability", "aws-smithy-runtime-api", "aws-smithy-types", @@ -695,9 +677,9 @@ dependencies = [ [[package]] name = "aws-smithy-runtime-api" -version = "1.9.0" +version = "1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07f5e0fc8a6b3f2303f331b94504bbf754d85488f402d6f1dd7a6080f99afe56" +checksum = "3683c5b152d2ad753607179ed71988e8cfd52964443b4f74fd8e552d0bbfeb46" dependencies = [ "aws-smithy-async", "aws-smithy-types", @@ -712,9 +694,9 @@ dependencies = [ [[package]] name = "aws-smithy-types" -version = "1.3.2" +version = "1.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d498595448e43de7f4296b7b7a18a8a02c61ec9349128c80a368f7c3b4ab11a8" +checksum = "9f5b3a7486f6690ba25952cabf1e7d75e34d69eaff5081904a47bc79074d6457" dependencies = [ "base64-simd", "bytes", @@ -738,18 +720,18 @@ dependencies = [ [[package]] name = "aws-smithy-xml" -version = "0.60.10" +version = "0.60.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3db87b96cb1b16c024980f133968d52882ca0daaee3a086c6decc500f6c99728" +checksum = "e9c34127e8c624bc2999f3b657e749c1393bedc9cd97b92a804db8ced4d2e163" dependencies = [ "xmlparser", ] [[package]] name = "aws-types" -version = "1.3.7" +version = "1.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a322fec39e4df22777ed3ad8ea868ac2f94cd15e1a55f6ee8d8d6305057689a" +checksum = "e2fd329bf0e901ff3f60425691410c69094dc2a1f34b331f37bfc4e9ac1565a1" dependencies = [ "aws-credential-types", "aws-smithy-async", @@ -791,12 +773,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "349a06037c7bf932dd7e7d1f653678b2038b9ad46a74102f1fc7bd7872678cce" -[[package]] -name = "base64" -version = "0.21.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" - [[package]] name = "base64" version = "0.22.1" @@ -1451,12 +1427,12 @@ dependencies = [ [[package]] name = "deranged" -version = "0.4.0" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" +checksum = "a41953f86f8a05768a6cda24def994fd2f424b04ec5c719cf89989779f199071" dependencies = [ "powerfmt", - "serde", + "serde_core", ] [[package]] @@ -2008,19 +1984,19 @@ dependencies = [ [[package]] name = "google-cloud-auth" -version = "0.22.2" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290a18c1cc7c02934c1b88920af8b55ff588ff2ef47f35dae5fc1f10f2056538" +checksum = "c5a0f0ef58bc79d636e95db264939a6f3fd80951f77743f2b7ec55e22171150d" dependencies = [ "async-trait", - "base64 0.22.1", + "base64", "bon", "google-cloud-gax", "http 1.3.1", "reqwest", "rustc_version", - "rustls 0.23.29", - "rustls-pemfile 2.2.0", + "rustls", + "rustls-pemfile", "serde", "serde_json", "thiserror 2.0.16", @@ -2030,11 +2006,11 @@ dependencies = [ [[package]] name = "google-cloud-gax" -version = "0.23.2" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5560b3cedeea7041862717175d98733ac81f7bedfdd0c7949bf06d761bf4094f" +checksum = "58bc95deae841e35758fa5caba317092f26940135c7184570feb691a1844db08" dependencies = [ - "base64 0.22.1", + "base64", "bytes", "futures", "google-cloud-rpc", @@ -2050,9 +2026,9 @@ dependencies = [ [[package]] name = "google-cloud-rpc" -version = "0.4.2" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "949f5858642f68efbd836a82400d04f2572e6d18a612c1144d0868832f40996e" +checksum = "e5b655e3540a78e18fd753ebd8f11e068210a3fa392892370f932ffcc8774346" dependencies = [ "bytes", "google-cloud-wkt", @@ -2063,11 +2039,11 @@ dependencies = [ [[package]] name = "google-cloud-wkt" -version = "0.5.2" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e2916ee3f086766d6989abd8a0b1c3910322af60d72352f6cdf5cb8eb951464" +checksum = "02931df6af9beda1c852bbbbe5f7b6ba6ae5e4cd49c029fa0ca2cecc787cd9b1" dependencies = [ - "base64 0.22.1", + "base64", "bytes", "serde", "serde_json", @@ -2088,25 +2064,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "h2" -version = "0.3.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0beca50380b1fc32983fc1cb4587bfa4bb9e78fc259aad4a0032d2080309222d" -dependencies = [ - "bytes", - "fnv", - "futures-core", - "futures-sink", - "futures-util", - "http 0.2.12", - "indexmap 2.10.0", - "slab", - "tokio", - "tokio-util", - "tracing", -] - [[package]] name = "h2" version = "0.4.11" @@ -2292,12 +2249,6 @@ version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" -[[package]] -name = "httpdate" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" - [[package]] name = "humansize" version = "2.1.3" @@ -2313,30 +2264,6 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b112acc8b3adf4b107a8ec20977da0273a8c386765a3ec0229bd500a1443f9f" -[[package]] -name = "hyper" -version = "0.14.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7" -dependencies = [ - "bytes", - "futures-channel", - "futures-core", - "futures-util", - "h2 0.3.27", - "http 0.2.12", - "http-body 0.4.6", - "httparse", - "httpdate", - "itoa", - "pin-project-lite", - "socket2", - "tokio", - "tower-service", - "tracing", - "want", -] - [[package]] name = "hyper" version = "1.6.0" @@ -2346,7 +2273,7 @@ dependencies = [ "bytes", "futures-channel", "futures-util", - "h2 0.4.11", + "h2", "http 1.3.1", "http-body 1.0.1", "httparse", @@ -2357,22 +2284,6 @@ dependencies = [ "want", ] -[[package]] -name = "hyper-rustls" -version = "0.24.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" -dependencies = [ - "futures-util", - "http 0.2.12", - "hyper 0.14.32", - "log", - "rustls 0.21.12", - "rustls-native-certs 0.6.3", - "tokio", - "tokio-rustls 0.24.1", -] - [[package]] name = "hyper-rustls" version = "0.27.7" @@ -2380,13 +2291,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" dependencies = [ "http 1.3.1", - "hyper 1.6.0", + "hyper", "hyper-util", - "rustls 0.23.29", - "rustls-native-certs 0.8.1", + "rustls", + "rustls-native-certs", "rustls-pki-types", "tokio", - "tokio-rustls 0.26.2", + "tokio-rustls", "tower-service", "webpki-roots", ] @@ -2399,7 +2310,7 @@ checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" dependencies = [ "bytes", "http-body-util", - "hyper 1.6.0", + "hyper", "hyper-util", "native-tls", "tokio", @@ -2409,18 +2320,18 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.15" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f66d5bd4c6f02bf0542fad85d626775bab9258cf795a4256dcaf3161114d1df" +checksum = "3c6995591a8f1380fcb4ba966a252a4b29188d51d2b89e3a252f5305be65aea8" dependencies = [ - "base64 0.22.1", + "base64", "bytes", "futures-channel", "futures-core", "futures-util", "http 1.3.1", "http-body 1.0.1", - "hyper 1.6.0", + "hyper", "ipnet", "libc", "percent-encoding", @@ -3151,7 +3062,7 @@ checksum = "ffb9838d0575c6dbaf3fcec7255af8d5771996d4af900bbb6fa9a314dec00a1a" dependencies = [ "anyhow", "backon", - "base64 0.22.1", + "base64", "bytes", "chrono", "crc32c", @@ -3314,7 +3225,7 @@ checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" [[package]] name = "path_resolver" -version = "0.2.0" +version = "0.2.1" dependencies = [ "fs-err", "fxhash", @@ -3443,7 +3354,7 @@ version = "1.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3af6b589e163c5a788fab00ce0c0366f6efbb9959c2f9874b224936af7fce7e1" dependencies = [ - "base64 0.22.1", + "base64", "indexmap 2.10.0", "quick-xml 0.38.0", "serde", @@ -3567,7 +3478,7 @@ dependencies = [ [[package]] name = "py-rattler" -version = "0.15.0" +version = "0.16.0" dependencies = [ "anyhow", "chrono", @@ -3735,7 +3646,7 @@ dependencies = [ "quinn-proto", "quinn-udp", "rustc-hash", - "rustls 0.23.29", + "rustls", "socket2", "thiserror 2.0.16", "tokio", @@ -3755,7 +3666,7 @@ dependencies = [ "rand 0.9.2", "ring", "rustc-hash", - "rustls 0.23.29", + "rustls", "rustls-pki-types", "slab", "thiserror 2.0.16", @@ -3869,7 +3780,7 @@ dependencies = [ [[package]] name = "rattler" -version = "0.37.4" +version = "0.37.7" dependencies = [ "anyhow", "console", @@ -3911,7 +3822,7 @@ dependencies = [ [[package]] name = "rattler_cache" -version = "0.3.34" +version = "0.3.37" dependencies = [ "anyhow", "dashmap", @@ -3942,7 +3853,7 @@ dependencies = [ [[package]] name = "rattler_conda_types" -version = "0.39.2" +version = "0.40.0" dependencies = [ "chrono", "core-foundation 0.10.1", @@ -3981,7 +3892,7 @@ dependencies = [ [[package]] name = "rattler_config" -version = "0.2.9" +version = "0.2.11" dependencies = [ "console", "fs-err", @@ -4013,7 +3924,7 @@ dependencies = [ [[package]] name = "rattler_index" -version = "0.25.2" +version = "0.25.6" dependencies = [ "anyhow", "bytes", @@ -4047,7 +3958,7 @@ dependencies = [ [[package]] name = "rattler_lock" -version = "0.24.1" +version = "0.25.0" dependencies = [ "chrono", "file_url", @@ -4079,7 +3990,7 @@ dependencies = [ [[package]] name = "rattler_menuinst" -version = "0.2.27" +version = "0.2.29" dependencies = [ "chrono", "configparser", @@ -4107,14 +4018,15 @@ dependencies = [ [[package]] name = "rattler_networking" -version = "0.25.13" +version = "0.25.16" dependencies = [ "anyhow", "async-once-cell", "async-trait", "aws-config", "aws-sdk-s3", - "base64 0.22.1", + "aws-smithy-http-client", + "base64", "dirs", "fs-err", "getrandom 0.3.3", @@ -4136,7 +4048,7 @@ dependencies = [ [[package]] name = "rattler_package_streaming" -version = "0.23.4" +version = "0.23.7" dependencies = [ "bzip2", "chrono", @@ -4182,7 +4094,7 @@ dependencies = [ [[package]] name = "rattler_repodata_gateway" -version = "0.24.4" +version = "0.24.7" dependencies = [ "anyhow", "async-compression", @@ -4240,11 +4152,12 @@ dependencies = [ [[package]] name = "rattler_s3" -version = "0.1.2" +version = "0.1.5" dependencies = [ "aws-config", "aws-credential-types", "aws-sdk-s3", + "aws-smithy-http-client", "clap", "rattler_networking", "serde", @@ -4255,7 +4168,7 @@ dependencies = [ [[package]] name = "rattler_shell" -version = "0.25.0" +version = "0.25.2" dependencies = [ "anyhow", "enum_dispatch", @@ -4273,7 +4186,7 @@ dependencies = [ [[package]] name = "rattler_solve" -version = "3.0.3" +version = "3.0.5" dependencies = [ "chrono", "futures", @@ -4289,7 +4202,7 @@ dependencies = [ [[package]] name = "rattler_virtual_packages" -version = "2.1.5" +version = "2.2.1" dependencies = [ "archspec", "libloading", @@ -4434,7 +4347,7 @@ checksum = "43451dbf3590a7590684c25fb8d12ecdcc90ed3ac123433e500447c7d77ed701" dependencies = [ "anyhow", "async-trait", - "base64 0.22.1", + "base64", "chrono", "form_urlencoded", "getrandom 0.2.16", @@ -4462,17 +4375,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d429f34c8092b2d42c7c93cec323bb4adeb7c67698f70839adec842ec10c7ceb" dependencies = [ "async-compression", - "base64 0.22.1", + "base64", "bytes", "encoding_rs", "futures-core", "futures-util", - "h2 0.4.11", + "h2", "http 1.3.1", "http-body 1.0.1", "http-body-util", - "hyper 1.6.0", - "hyper-rustls 0.27.7", + "hyper", + "hyper-rustls", "hyper-tls", "hyper-util", "js-sys", @@ -4482,8 +4395,8 @@ dependencies = [ "percent-encoding", "pin-project-lite", "quinn", - "rustls 0.23.29", - "rustls-native-certs 0.8.1", + "rustls", + "rustls-native-certs", "rustls-pki-types", "serde", "serde_json", @@ -4491,7 +4404,7 @@ dependencies = [ "sync_wrapper", "tokio", "tokio-native-tls", - "tokio-rustls 0.26.2", + "tokio-rustls", "tokio-util", "tower", "tower-http", @@ -4651,43 +4564,19 @@ dependencies = [ [[package]] name = "rustls" -version = "0.21.12" +version = "0.23.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" -dependencies = [ - "log", - "ring", - "rustls-webpki 0.101.7", - "sct", -] - -[[package]] -name = "rustls" -version = "0.23.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2491382039b29b9b11ff08b76ff6c97cf287671dbb74f0be44bda389fffe9bd1" +checksum = "cd3c25631629d034ce7cd9940adc9d45762d46de2b0f57193c4443b92c6d4d40" dependencies = [ "log", "once_cell", "ring", "rustls-pki-types", - "rustls-webpki 0.103.4", + "rustls-webpki", "subtle", "zeroize", ] -[[package]] -name = "rustls-native-certs" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00" -dependencies = [ - "openssl-probe", - "rustls-pemfile 1.0.4", - "schannel", - "security-framework 2.11.1", -] - [[package]] name = "rustls-native-certs" version = "0.8.1" @@ -4700,15 +4589,6 @@ dependencies = [ "security-framework 3.2.0", ] -[[package]] -name = "rustls-pemfile" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" -dependencies = [ - "base64 0.21.7", -] - [[package]] name = "rustls-pemfile" version = "2.2.0" @@ -4730,19 +4610,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.101.7" +version = "0.103.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" -dependencies = [ - "ring", - "untrusted", -] - -[[package]] -name = "rustls-webpki" -version = "0.103.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a17884ae0c1b773f1ccd2bd4a8c72f16da897310a98b0e84bf349ad5ead92fc" +checksum = "e10b3f4191e8a80e6b43eebabfac91e5dcecebb27a71f04e820c47ec41d314bf" dependencies = [ "ring", "rustls-pki-types", @@ -4821,16 +4691,6 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" -[[package]] -name = "sct" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" -dependencies = [ - "ring", - "untrusted", -] - [[package]] name = "sec1" version = "0.3.0" @@ -4917,10 +4777,11 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.219" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" dependencies = [ + "serde_core", "serde_derive", ] @@ -4945,11 +4806,20 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + [[package]] name = "serde_derive" -version = "1.0.219" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", @@ -5007,7 +4877,7 @@ version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2c45cd61fefa9db6f254525d46e392b852e0e61d9a1fd36e5bd183450a556d5" dependencies = [ - "base64 0.22.1", + "base64", "chrono", "hex", "indexmap 1.9.3", @@ -5400,9 +5270,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.41" +version = "0.3.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" +checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d" dependencies = [ "deranged", "itoa", @@ -5415,15 +5285,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.4" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" +checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b" [[package]] name = "time-macros" -version = "0.2.22" +version = "0.2.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49" +checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3" dependencies = [ "num-conv", "time-core", @@ -5504,23 +5374,13 @@ dependencies = [ "tokio", ] -[[package]] -name = "tokio-rustls" -version = "0.24.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" -dependencies = [ - "rustls 0.21.12", - "tokio", -] - [[package]] name = "tokio-rustls" version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b" dependencies = [ - "rustls 0.23.29", + "rustls", "tokio", ] diff --git a/py-rattler/src/solver.rs b/py-rattler/src/solver.rs index 9c749e9a2..5e246f243 100644 --- a/py-rattler/src/solver.rs +++ b/py-rattler/src/solver.rs @@ -4,6 +4,7 @@ use pyo3::{ FromPyObject, PyAny, PyErr, PyResult, Python, }; use pyo3_async_runtimes::tokio::future_into_py; +use rattler_conda_types::PackageName; use rattler_repodata_gateway::sparse::SparseRepoData; use rattler_solve::{resolvo::Solver, RepoDataIter, SolveStrategy, SolverImpl, SolverTask}; use tokio::task::JoinError; @@ -156,9 +157,13 @@ pub fn py_solve_with_sparse_repodata<'py>( }) .collect::, _>>()?; - let package_names = specs - .iter() - .filter_map(|match_spec| match_spec.inner.name.as_ref().map(|n| Option::::from(n.clone()).flatten())); + let package_names = specs.iter().filter_map(|match_spec| { + match_spec + .inner + .name + .as_ref() + .and_then(|n| Option::::from(n.clone())) + }); let available_packages = SparseRepoData::load_records_recursive( repo_data_refs, From 494e72bc39f114d43365c70b9aef37b58383192b Mon Sep 17 00:00:00 2001 From: Pavel Zwerschke Date: Fri, 10 Oct 2025 11:47:26 +0200 Subject: [PATCH 12/18] wip --- crates/rattler_solve/benches/bench.rs | 6 ++++-- crates/rattler_solve/benches/sorting_bench.rs | 4 ++-- crates/rattler_solve/tests/backends.rs | 9 +++------ 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/crates/rattler_solve/benches/bench.rs b/crates/rattler_solve/benches/bench.rs index f905c6e0c..97feed235 100644 --- a/crates/rattler_solve/benches/bench.rs +++ b/crates/rattler_solve/benches/bench.rs @@ -1,6 +1,6 @@ use criterion::{criterion_group, criterion_main, Criterion, SamplingMode}; use rattler_conda_types::ParseStrictness::Strict; -use rattler_conda_types::{Channel, ChannelConfig, MatchSpec}; +use rattler_conda_types::{Channel, ChannelConfig, MatchSpec, PackageName}; use rattler_repodata_gateway::sparse::{PackageFormatSelection, SparseRepoData}; use rattler_solve::{SolverImpl, SolverTask}; use std::hint::black_box; @@ -55,7 +55,9 @@ fn bench_solve_environment(c: &mut Criterion, specs: Vec<&str>) { read_sparse_repodata(&json_file_noarch), ]; - let names = specs.iter().map(|s| s.name.clone().unwrap()); + let names = specs + .iter() + .map(|s| Option::::from(s.name.clone().unwrap()).unwrap()); let available_packages = SparseRepoData::load_records_recursive( &sparse_repo_data, names, diff --git a/crates/rattler_solve/benches/sorting_bench.rs b/crates/rattler_solve/benches/sorting_bench.rs index 772ee296d..659e377f4 100644 --- a/crates/rattler_solve/benches/sorting_bench.rs +++ b/crates/rattler_solve/benches/sorting_bench.rs @@ -2,7 +2,7 @@ use std::{hint::black_box, path::Path}; use criterion::{criterion_group, criterion_main, BatchSize, Criterion}; use futures::FutureExt; -use rattler_conda_types::{Channel, MatchSpec}; +use rattler_conda_types::{Channel, MatchSpec, PackageName}; use rattler_repodata_gateway::sparse::{PackageFormatSelection, SparseRepoData}; use rattler_solve::{resolvo::CondaDependencyProvider, ChannelPriority}; use resolvo::SolverCache; @@ -10,7 +10,7 @@ use resolvo::SolverCache; fn bench_sort(c: &mut Criterion, sparse_repo_data: &SparseRepoData, spec: &str) { let match_spec = MatchSpec::from_str(spec, rattler_conda_types::ParseStrictness::Lenient).unwrap(); - let package_name = match_spec.name.clone().unwrap(); + let package_name = Option::::from(match_spec.name.clone().unwrap()).unwrap(); let repodata = SparseRepoData::load_records_recursive( [sparse_repo_data], diff --git a/crates/rattler_solve/tests/backends.rs b/crates/rattler_solve/tests/backends.rs index 2a1549f86..650ea20ca 100644 --- a/crates/rattler_solve/tests/backends.rs +++ b/crates/rattler_solve/tests/backends.rs @@ -135,8 +135,7 @@ fn solve_real_world(specs: Vec<&str>) -> Vec { let names = specs.iter().filter_map(|s| { s.name .as_ref() - .map(|n| Option::::from(n.clone())) - .flatten() + .and_then(|n| Option::::from(n.clone())) }); let available_packages = SparseRepoData::load_records_recursive( sparse_repo_data, @@ -1164,8 +1163,7 @@ fn compare_solve(task: CompareTask<'_>) { let names = specs.iter().filter_map(|s| { s.name .as_ref() - .map(|n| Option::::from(n.clone())) - .flatten() + .and_then(|n| Option::::from(n.clone())) }); let available_packages = SparseRepoData::load_records_recursive( sparse_repo_data, @@ -1305,8 +1303,7 @@ fn solve_to_get_channel_of_spec( let names = specs.iter().filter_map(|s| { s.name .as_ref() - .map(|n| Option::::from(n.clone())) - .flatten() + .and_then(|n| Option::::from(n.clone())) }); let available_packages = SparseRepoData::load_records_recursive( From 2d33ef1fa11b91047ea03bd1b9ff5343cff50078 Mon Sep 17 00:00:00 2001 From: Pavel Zwerschke Date: Fri, 10 Oct 2025 14:53:49 +0200 Subject: [PATCH 13/18] fix --- py-rattler/rattler/match_spec/match_spec.py | 2 +- .../rattler/package/package_name_matcher.py | 50 +++++++++++++++++++ py-rattler/src/lib.rs | 8 +-- py-rattler/src/match_spec.rs | 2 +- py-rattler/src/package_name.rs | 2 +- py-rattler/src/package_name_matcher.rs | 32 +++++++----- 6 files changed, 77 insertions(+), 19 deletions(-) create mode 100644 py-rattler/rattler/package/package_name_matcher.py diff --git a/py-rattler/rattler/match_spec/match_spec.py b/py-rattler/rattler/match_spec/match_spec.py index fcb5c0bf2..074dac473 100644 --- a/py-rattler/rattler/match_spec/match_spec.py +++ b/py-rattler/rattler/match_spec/match_spec.py @@ -203,7 +203,7 @@ def from_nameless(cls, spec: NamelessMatchSpec, name: str) -> MatchSpec: MatchSpec("foo ==3.4") >>> MatchSpec.from_nameless(spec, "$foo") # doctest: +IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): - exceptions.InvalidPackageNameException + exceptions.PackageNameMatcherParseException >>> ``` """ diff --git a/py-rattler/rattler/package/package_name_matcher.py b/py-rattler/rattler/package/package_name_matcher.py new file mode 100644 index 000000000..12cefb5b8 --- /dev/null +++ b/py-rattler/rattler/package/package_name_matcher.py @@ -0,0 +1,50 @@ +from typing import Union + +from rattler.package.package_name import PackageName +from rattler.rattler import PyPackageNameMatcher + + +class PackageNameMatcher: + """ + A class representing a package name matcher. + + Examples + -------- + ```python + >>> PackageNameMatcher("rattler") + PackageNameMatcher("rattler", exact) + >>> PackageNameMatcher("jupyter-*") + PackageNameMatcher("jupyter-*", glob) + >>> PackageNameMatcher("^jupyter-.*$") + PackageNameMatcher("^jupyter-.*$", regex) + >>> + ``` + """ + + _package_name_matcher: PyPackageNameMatcher + + def __init__(self, package_name_matcher: str): + self._package_name_matcher = PyPackageNameMatcher(package_name_matcher) + + def __repr__(self) -> str: + inner = self._package_name_matcher.display_inner() + return f"{type(self).__name__}({inner})" + + def as_package_name(self) -> Union[PackageName, None]: + """ + Converts a PackageNameMatcher to a PackageName if it is an exact matcher. + + Examples + -------- + ```python + >>> PackageNameMatcher("rattler").as_package_name() + PackageName("rattler") + >>> PackageNameMatcher("jupyter-*").as_package_name() + >>> PackageNameMatcher("^jupyter-.*$").as_package_name() + >>> + ``` + """ + py_package_name = self._package_name_matcher.as_package_name() + if py_package_name is None: + return None + return PackageName._from_py_package_name(py_package_name) diff --git a/py-rattler/src/lib.rs b/py-rattler/src/lib.rs index 89314c194..f4461f3d5 100644 --- a/py-rattler/src/lib.rs +++ b/py-rattler/src/lib.rs @@ -35,10 +35,10 @@ use channel::{PyChannel, PyChannelConfig, PyChannelPriority}; use error::{ ActivationException, CacheDirException, ConvertSubdirException, DetectVirtualPackageException, EnvironmentCreationException, ExtractException, FetchRepoDataException, - InvalidChannelException, InvalidMatchSpecException, InvalidPackageNameException, PackageNameMatcherParseException, - InvalidUrlException, InvalidVersionException, IoException, LinkException, ParseArchException, - ParsePlatformException, PyRattlerError, SolverException, TransactionException, - ValidatePackageRecordsException, VersionBumpException, + InvalidChannelException, InvalidMatchSpecException, InvalidPackageNameException, + InvalidUrlException, InvalidVersionException, IoException, LinkException, + PackageNameMatcherParseException, ParseArchException, ParsePlatformException, PyRattlerError, + SolverException, TransactionException, ValidatePackageRecordsException, VersionBumpException, }; use explicit_environment_spec::{PyExplicitEnvironmentEntry, PyExplicitEnvironmentSpec}; use generic_virtual_package::PyGenericVirtualPackage; diff --git a/py-rattler/src/match_spec.rs b/py-rattler/src/match_spec.rs index 0be8335e8..787087630 100644 --- a/py-rattler/src/match_spec.rs +++ b/py-rattler/src/match_spec.rs @@ -1,5 +1,5 @@ -use std::{borrow::Borrow, str::FromStr}; use std::sync::Arc; +use std::{borrow::Borrow, str::FromStr}; use pyo3::{pyclass, pymethods, types::PyBytes, Bound, PyResult, Python}; use rattler_conda_types::{Channel, MatchSpec, Matches, PackageNameMatcher, ParseStrictness}; diff --git a/py-rattler/src/package_name.rs b/py-rattler/src/package_name.rs index b3af6b8fe..0e7e5e52e 100644 --- a/py-rattler/src/package_name.rs +++ b/py-rattler/src/package_name.rs @@ -10,7 +10,7 @@ use crate::error::PyRattlerError; #[pyclass] #[repr(transparent)] -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct PyPackageName { pub(crate) inner: PackageName, } diff --git a/py-rattler/src/package_name_matcher.rs b/py-rattler/src/package_name_matcher.rs index 96e9d6ef2..d6eccf889 100644 --- a/py-rattler/src/package_name_matcher.rs +++ b/py-rattler/src/package_name_matcher.rs @@ -1,15 +1,15 @@ use std::{ collections::hash_map::DefaultHasher, - hash::{Hash, Hasher}, str::FromStr, + hash::{Hash, Hasher}, + str::FromStr, }; use pyo3::{pyclass, pymethods}; use rattler_conda_types::{PackageName, PackageNameMatcher}; -use crate::error::PyRattlerError; +use crate::{error::PyRattlerError, package_name::PyPackageName}; #[pyclass] -#[repr(transparent)] #[derive(Clone)] pub struct PyPackageNameMatcher { pub(crate) inner: PackageNameMatcher, @@ -33,17 +33,25 @@ impl PyPackageNameMatcher { /// valid or normalized conda package name. #[new] pub fn new(source: String) -> pyo3::PyResult { - Ok(PackageNameMatcher::from_str(&source) - .map(Into::into) - .map_err(PyRattlerError::from)?) + let inner = PackageNameMatcher::from_str(source.as_str()).map_err(PyRattlerError::from)?; + + Ok(Self { inner }) + } + + fn display_inner(&self) -> String { + match self.inner { + PackageNameMatcher::Exact(ref name) => { + format!("\"{}\", exact", name.as_source()) + } + PackageNameMatcher::Glob(ref glob) => format!("\"{}\", glob", glob), + PackageNameMatcher::Regex(ref regex) => { + format!("\"{}\", regex", regex) + } + } } - /// Constructs a new exact `PackageNameMatcher` from a string without checking if the string is actually a - /// valid or normalized conda package name. This should only be used if you are sure that the - /// input string is valid. - #[staticmethod] - pub fn new_unchecked(normalized: String) -> Self { - PackageNameMatcher::Exact(PackageName::new_unchecked(normalized)).into() + fn as_package_name(&self) -> Option { + Option::::from(self.inner.clone()).map(PyPackageName::from) } /// Compute the hash of the name. From d3c2fbc99c3b2292fc39bc85d799d4813b099d29 Mon Sep 17 00:00:00 2001 From: Pavel Zwerschke Date: Fri, 10 Oct 2025 15:12:51 +0200 Subject: [PATCH 14/18] fix --- py-rattler/rattler/match_spec/match_spec.py | 6 +++--- py-rattler/rattler/package/package_name_matcher.py | 9 +++++++++ py-rattler/tests/unit/test_matchspec.py | 4 +++- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/py-rattler/rattler/match_spec/match_spec.py b/py-rattler/rattler/match_spec/match_spec.py index 074dac473..b58855975 100644 --- a/py-rattler/rattler/match_spec/match_spec.py +++ b/py-rattler/rattler/match_spec/match_spec.py @@ -3,7 +3,7 @@ from typing import TYPE_CHECKING, Optional from rattler.channel.channel import Channel -from rattler.package.package_name import PackageName +from rattler.package.package_name_matcher import PackageNameMatcher from rattler.rattler import PyMatchSpec if TYPE_CHECKING: @@ -104,11 +104,11 @@ def __init__(self, spec: str, strict: bool = False) -> None: ) @property - def name(self) -> Optional[PackageName]: + def name(self) -> Optional[PackageNameMatcher]: """ The name of the package. """ - return PackageName._from_py_package_name(self._match_spec.name) + return PackageNameMatcher._from_py_package_name_matcher(self._match_spec.name) @property def version(self) -> Optional[str]: diff --git a/py-rattler/rattler/package/package_name_matcher.py b/py-rattler/rattler/package/package_name_matcher.py index 12cefb5b8..bf7ec6a35 100644 --- a/py-rattler/rattler/package/package_name_matcher.py +++ b/py-rattler/rattler/package/package_name_matcher.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from typing import Union from rattler.package.package_name import PackageName @@ -30,6 +32,13 @@ def __repr__(self) -> str: inner = self._package_name_matcher.display_inner() return f"{type(self).__name__}({inner})" + @classmethod + def _from_py_package_name_matcher(cls, py_package_name_matcher: PyPackageNameMatcher) -> PackageNameMatcher: + """Construct Rattler PackageNameMatcher from FFI PyPackageName object.""" + package_name_matcher = cls.__new__(cls) + package_name_matcher._package_name_matcher = py_package_name_matcher + return package_name_matcher + def as_package_name(self) -> Union[PackageName, None]: """ Converts a PackageNameMatcher to a PackageName if it is an exact matcher. diff --git a/py-rattler/tests/unit/test_matchspec.py b/py-rattler/tests/unit/test_matchspec.py index ff704aee0..e430f2bd1 100644 --- a/py-rattler/tests/unit/test_matchspec.py +++ b/py-rattler/tests/unit/test_matchspec.py @@ -65,7 +65,9 @@ def test_parse_no_channel() -> None: m = MatchSpec("python[version=3.9]") assert m.channel is None assert m.name is not None - assert m.name.normalized == "python" + package_name = m.name.as_package_name() + assert package_name is not None + assert package_name.normalized == "python" assert m.version == "==3.9" From a127ba51ab3965b8468d36abdd04fd6b00d4f3a8 Mon Sep 17 00:00:00 2001 From: Pavel Zwerschke Date: Fri, 10 Oct 2025 15:21:26 +0200 Subject: [PATCH 15/18] Delete crates/rattler_solve/tests/snapshots/backends__resolvo__issue_717.snap.new --- .../tests/snapshots/backends__resolvo__issue_717.snap.new | 8 -------- 1 file changed, 8 deletions(-) delete mode 100644 crates/rattler_solve/tests/snapshots/backends__resolvo__issue_717.snap.new diff --git a/crates/rattler_solve/tests/snapshots/backends__resolvo__issue_717.snap.new b/crates/rattler_solve/tests/snapshots/backends__resolvo__issue_717.snap.new deleted file mode 100644 index 91573419f..000000000 --- a/crates/rattler_solve/tests/snapshots/backends__resolvo__issue_717.snap.new +++ /dev/null @@ -1,8 +0,0 @@ ---- -source: crates/rattler_solve/tests/backends.rs -assertion_line: 774 -expression: result.unwrap_err() ---- -Cannot solve the request because of: The following packages are incompatible -└─ issue_717 * cannot be installed because there are no viable options: - └─ issue_717 2.1 is excluded because the constrains 'ray[default,data] >=2.9.0,<3.0.0' failed to parse: invalid package name ray[default,data]: 'ray[default,data]' is not a valid package name. Package names can only contain 0-9, a-z, A-Z, -, _, or . From 046912fa896ff19a040a56adb901455511899b1f Mon Sep 17 00:00:00 2001 From: Pavel Zwerschke Date: Fri, 10 Oct 2025 15:30:15 +0200 Subject: [PATCH 16/18] fix --- py-rattler/src/error.rs | 4 +++- py-rattler/src/package_name_matcher.rs | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/py-rattler/src/error.rs b/py-rattler/src/error.rs index b970fc6f1..047c260f2 100644 --- a/py-rattler/src/error.rs +++ b/py-rattler/src/error.rs @@ -4,7 +4,9 @@ use pyo3::exceptions::PyValueError; use pyo3::{create_exception, exceptions::PyException, PyErr}; use rattler::install::TransactionError; use rattler_conda_types::{ - ConvertSubdirError, InvalidPackageNameError, PackageNameMatcherParseError, ParseArchError, ParseChannelError, ParseMatchSpecError, ParsePlatformError, ParseVersionError, ValidatePackageRecordsError, VersionBumpError, VersionExtendError + ConvertSubdirError, InvalidPackageNameError, PackageNameMatcherParseError, ParseArchError, + ParseChannelError, ParseMatchSpecError, ParsePlatformError, ParseVersionError, + ValidatePackageRecordsError, VersionBumpError, VersionExtendError, }; use rattler_lock::{ConversionError, ParseCondaLockError}; use rattler_networking::authentication_storage::AuthenticationStorageError; diff --git a/py-rattler/src/package_name_matcher.rs b/py-rattler/src/package_name_matcher.rs index d6eccf889..b71e280eb 100644 --- a/py-rattler/src/package_name_matcher.rs +++ b/py-rattler/src/package_name_matcher.rs @@ -43,9 +43,9 @@ impl PyPackageNameMatcher { PackageNameMatcher::Exact(ref name) => { format!("\"{}\", exact", name.as_source()) } - PackageNameMatcher::Glob(ref glob) => format!("\"{}\", glob", glob), + PackageNameMatcher::Glob(ref glob) => format!("\"{glob}\", glob"), PackageNameMatcher::Regex(ref regex) => { - format!("\"{}\", regex", regex) + format!("\"{regex}\", regex") } } } From ac969ce857b2ff669a796bd15db24946fab99044 Mon Sep 17 00:00:00 2001 From: Pavel Zwerschke Date: Fri, 10 Oct 2025 15:54:01 +0200 Subject: [PATCH 17/18] fix --- .../tests/snapshots/backends__resolvo__issue_717.snap | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/rattler_solve/tests/snapshots/backends__resolvo__issue_717.snap b/crates/rattler_solve/tests/snapshots/backends__resolvo__issue_717.snap index c24dcaebb..4a33e63fc 100644 --- a/crates/rattler_solve/tests/snapshots/backends__resolvo__issue_717.snap +++ b/crates/rattler_solve/tests/snapshots/backends__resolvo__issue_717.snap @@ -4,4 +4,4 @@ expression: result.unwrap_err() --- Cannot solve the request because of: The following packages are incompatible └─ issue_717 * cannot be installed because there are no viable options: - └─ issue_717 2.1 is excluded because the constrains 'ray[default,data] >=2.9.0,<3.0.0' failed to parse: 'ray[default,data]' is not a valid package name. Package names can only contain 0-9, a-z, A-Z, -, _, or . + └─ issue_717 2.1 is excluded because the constrains 'ray[default,data] >=2.9.0,<3.0.0' failed to parse: invalid package name ray[default,data]: 'ray[default,data]' is not a valid package name. Package names can only contain 0-9, a-z, A-Z, -, _, or . From 39ef192a0cfcf343dec8920bda53bf2612830d6d Mon Sep 17 00:00:00 2001 From: Pavel Zwerschke Date: Fri, 10 Oct 2025 17:14:08 +0200 Subject: [PATCH 18/18] fix --- crates/rattler_conda_types/src/match_spec/mod.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/crates/rattler_conda_types/src/match_spec/mod.rs b/crates/rattler_conda_types/src/match_spec/mod.rs index 031333271..198818688 100644 --- a/crates/rattler_conda_types/src/match_spec/mod.rs +++ b/crates/rattler_conda_types/src/match_spec/mod.rs @@ -82,41 +82,41 @@ use package_name_matcher::PackageNameMatcher; /// # Examples: /// /// ```rust -/// use rattler_conda_types::{MatchSpec, VersionSpec, StringMatcher, PackageName, Channel, ChannelConfig, ParseStrictness::*}; +/// use rattler_conda_types::{MatchSpec, VersionSpec, StringMatcher, PackageNameMatcher, PackageName, Channel, ChannelConfig, ParseStrictness::*}; /// use std::str::FromStr; /// use std::sync::Arc; /// /// let channel_config = ChannelConfig::default_with_root_dir(std::env::current_dir().unwrap()); /// let spec = MatchSpec::from_str("foo 1.0.* py27_0", Strict).unwrap(); -/// assert_eq!(spec.name, Some(PackageName::new_unchecked("foo"))); +/// assert_eq!(spec.name, Some(PackageNameMatcher::Exact(PackageName::new_unchecked("foo")))); /// assert_eq!(spec.version, Some(VersionSpec::from_str("1.0.*", Strict).unwrap())); /// assert_eq!(spec.build, Some(StringMatcher::from_str("py27_0").unwrap())); /// /// let spec = MatchSpec::from_str("foo ==1.0 py27_0", Strict).unwrap(); -/// assert_eq!(spec.name, Some(PackageName::new_unchecked("foo"))); +/// assert_eq!(spec.name, Some(PackageNameMatcher::Exact(PackageName::new_unchecked("foo")))); /// assert_eq!(spec.version, Some(VersionSpec::from_str("==1.0", Strict).unwrap())); /// assert_eq!(spec.build, Some(StringMatcher::from_str("py27_0").unwrap())); /// /// let spec = MatchSpec::from_str(r#"conda-forge::foo[version="1.0.*"]"#, Strict).unwrap(); -/// assert_eq!(spec.name, Some(PackageName::new_unchecked("foo"))); +/// assert_eq!(spec.name, Some(PackageNameMatcher::Exact(PackageName::new_unchecked("foo")))); /// assert_eq!(spec.version, Some(VersionSpec::from_str("1.0.*", Strict).unwrap())); /// assert_eq!(spec.channel, Some(Channel::from_str("conda-forge", &channel_config).map(|channel| Arc::new(channel)).unwrap())); /// /// let spec = MatchSpec::from_str(r#"conda-forge::foo >=1.0[subdir="linux-64"]"#, Strict).unwrap(); -/// assert_eq!(spec.name, Some(PackageName::new_unchecked("foo"))); +/// assert_eq!(spec.name, Some(PackageNameMatcher::Exact(PackageName::new_unchecked("foo")))); /// assert_eq!(spec.version, Some(VersionSpec::from_str(">=1.0", Strict).unwrap())); /// assert_eq!(spec.channel, Some(Channel::from_str("conda-forge", &channel_config).map(|channel| Arc::new(channel)).unwrap())); /// assert_eq!(spec.subdir, Some("linux-64".to_string())); /// assert_eq!(spec, MatchSpec::from_str("conda-forge/linux-64::foo >=1.0", Strict).unwrap()); /// /// let spec = MatchSpec::from_str("*/linux-64::foo >=1.0", Strict).unwrap(); -/// assert_eq!(spec.name, Some(PackageName::new_unchecked("foo"))); +/// assert_eq!(spec.name, Some(PackageNameMatcher::Exact(PackageName::new_unchecked("foo")))); /// assert_eq!(spec.version, Some(VersionSpec::from_str(">=1.0", Strict).unwrap())); /// assert_eq!(spec.channel, Some(Channel::from_str("*", &channel_config).map(|channel| Arc::new(channel)).unwrap())); /// assert_eq!(spec.subdir, Some("linux-64".to_string())); /// /// let spec = MatchSpec::from_str(r#"foo[build="py2*"]"#, Strict).unwrap(); -/// assert_eq!(spec.name, Some(PackageName::new_unchecked("foo"))); +/// assert_eq!(spec.name, Some(PackageNameMatcher::Exact(PackageName::new_unchecked("foo")))); /// assert_eq!(spec.build, Some(StringMatcher::from_str("py2*").unwrap())); /// ``` ///