Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ regex = "1.11.1"
reqwest = { version = "0.12.15", default-features = false }
reqwest-middleware = "0.4.2"
reqwest-retry = "0.7.0"
resolvo = { version = "0.9.1" }
resolvo = { git = "https://github.com/baszalmstra/resolvo", branch = "condition-dependencies" }
# hold back at 0.4.0 until `reqwest-retry` is updated
retry-policies = { version = "0.4.0", default-features = false }
rmp-serde = { version = "1.3.0" }
Expand Down
15 changes: 15 additions & 0 deletions crates/rattler_conda_types/src/match_spec/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,8 @@ pub struct MatchSpec {
pub url: Option<Url>,
/// The license of the package
pub license: Option<String>,
/// The condition under which this dependency applies (e.g., "python >=3.12", "__unix")
pub condition: Option<String>,
}

impl Display for MatchSpec {
Expand Down Expand Up @@ -223,6 +225,10 @@ impl Display for MatchSpec {
write!(f, "[{}]", keys.join(", "))?;
}

if let Some(condition) = &self.condition {
write!(f, "; if {condition}")?;
}

Ok(())
}
}
Expand All @@ -245,6 +251,7 @@ impl MatchSpec {
sha256: self.sha256,
url: self.url,
license: self.license,
condition: self.condition,
},
)
}
Expand Down Expand Up @@ -302,6 +309,8 @@ pub struct NamelessMatchSpec {
pub url: Option<Url>,
/// The license of the package
pub license: Option<String>,
/// The condition under which this dependency applies (e.g., "python >=3.12", "__unix")
pub condition: Option<String>,
}

impl Display for NamelessMatchSpec {
Expand Down Expand Up @@ -329,6 +338,10 @@ impl Display for NamelessMatchSpec {
write!(f, "[{}]", keys.join(", "))?;
}

if let Some(condition) = &self.condition {
write!(f, "; if {condition}")?;
}

Ok(())
}
}
Expand All @@ -348,6 +361,7 @@ impl From<MatchSpec> for NamelessMatchSpec {
sha256: spec.sha256,
url: spec.url,
license: spec.license,
condition: spec.condition,
}
}
}
Expand All @@ -369,6 +383,7 @@ impl MatchSpec {
sha256: spec.sha256,
url: spec.url,
license: spec.license,
condition: spec.condition,
}
}
}
Expand Down
78 changes: 72 additions & 6 deletions crates/rattler_conda_types/src/match_spec/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,11 +135,18 @@ fn strip_comment(input: &str) -> (&str, Option<&str>) {
/// Strips any if statements from the matchspec. `if` statements in matchspec
/// are "anticipating future compatibility issues".
fn strip_if(input: &str) -> (&str, Option<&str>) {
// input
// .split_once("if")
// .map(|(spec, if_statement)| (spec, Some(if_statement)))
// .unwrap_or_else(|| (input, None))
(input, None)
// Look for "; if" or ";if" pattern
if let Some(idx) = input.find("; if ") {
let (spec, condition) = input.split_at(idx);
// Skip the "; if " part (5 characters)
(spec.trim(), Some(condition[5..].trim()))
} else if let Some(idx) = input.find(";if ") {
let (spec, condition) = input.split_at(idx);
// Skip the ";if " part (4 characters)
(spec.trim(), Some(condition[4..].trim()))
} else {
(input, None)
}
}

/// An optimized data structure to store key value pairs in between a bracket
Expand Down Expand Up @@ -614,7 +621,7 @@ fn matchspec_parser(
) -> Result<MatchSpec, ParseMatchSpecError> {
// Step 1. Strip '#' and `if` statement
let (input, _comment) = strip_comment(input);
let (input, _if_clause) = strip_if(input);
let (input, if_clause) = strip_if(input);

// 2. Strip off brackets portion
let (input, brackets) = strip_brackets(input.trim())?;
Expand Down Expand Up @@ -689,6 +696,11 @@ fn matchspec_parser(
match_spec.build = match_spec.build.or(build);
}

// Step 8. Add the condition if present
if let Some(condition) = if_clause {
match_spec.condition = Some(condition.to_owned());
}

Ok(match_spec)
}

Expand Down Expand Up @@ -1450,6 +1462,7 @@ mod tests {
.unwrap(),
),
license: Some("MIT".into()),
condition: None,
});

// insta check all the strings
Expand Down Expand Up @@ -1541,4 +1554,57 @@ mod tests {
// Missing opening bracket
assert!(MatchSpec::from_str("foo[extras=bar,baz]]", Strict).is_err());
}

#[test]
fn test_conditional_dependencies() {
// Test basic condition parsing
let spec = MatchSpec::from_str("foobar; if python >=3.12", Lenient).unwrap();
assert_eq!(spec.name.as_ref().unwrap().as_source(), "foobar");
assert_eq!(spec.condition, Some("python >=3.12".to_string()));

// Test with version and condition
let spec = MatchSpec::from_str("foobar >=1.0; if python >=3.12", Lenient).unwrap();
assert_eq!(spec.name.as_ref().unwrap().as_source(), "foobar");
assert_eq!(spec.version.as_ref().unwrap().to_string(), ">=1.0");
assert_eq!(spec.condition, Some("python >=3.12".to_string()));

// Test with virtual package condition
let spec = MatchSpec::from_str("bizbar 3.12.*; if __unix", Lenient).unwrap();
assert_eq!(spec.name.as_ref().unwrap().as_source(), "bizbar");
assert_eq!(spec.version.as_ref().unwrap().to_string(), "3.12.*");
assert_eq!(spec.condition, Some("__unix".to_string()));

// Test without space after semicolon
let spec = MatchSpec::from_str("package;if __linux", Lenient).unwrap();
assert_eq!(spec.name.as_ref().unwrap().as_source(), "package");
assert_eq!(spec.condition, Some("__linux".to_string()));

// Test with build string and condition
let spec = MatchSpec::from_str("numpy 1.21.* *py39*; if python ==3.9", Lenient).unwrap();
assert_eq!(spec.name.as_ref().unwrap().as_source(), "numpy");
assert_eq!(spec.version.as_ref().unwrap().to_string(), "1.21.*");
assert_eq!(spec.build.as_ref().unwrap().to_string(), "*py39*");
assert_eq!(spec.condition, Some("python ==3.9".to_string()));

// Test no condition
let spec = MatchSpec::from_str("foobar >=1.0", Lenient).unwrap();
assert_eq!(spec.name.as_ref().unwrap().as_source(), "foobar");
assert_eq!(spec.version.as_ref().unwrap().to_string(), ">=1.0");
assert_eq!(spec.condition, None);
}

#[test]
fn test_conditional_dependencies_display() {
// Test that display includes condition
let spec = MatchSpec::from_str("foobar >=1.0; if python >=3.12", Lenient).unwrap();
assert_eq!(spec.to_string(), "foobar >=1.0; if python >=3.12");

// Test display without condition
let spec = MatchSpec::from_str("foobar >=1.0", Lenient).unwrap();
assert_eq!(spec.to_string(), "foobar >=1.0");

// Test display with build and condition
let spec = MatchSpec::from_str("numpy 1.21.* *py39*; if python ==3.9", Lenient).unwrap();
assert_eq!(spec.to_string(), "numpy 1.21.* *py39*; if python ==3.9");
}
}
9 changes: 8 additions & 1 deletion crates/rattler_conda_types/src/package/index.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
use std::{collections::BTreeSet, path::Path};
use std::{
collections::{BTreeMap, BTreeSet},
path::Path,
};

use rattler_macros::sorted;
use serde::{Deserialize, Serialize};
Expand Down Expand Up @@ -35,6 +38,10 @@ pub struct IndexJson {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub depends: Vec<String>,

/// Extra dependency groups that can be selected using `foobar[extras=["scientific"]]`
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub extras: BTreeMap<String, Vec<String>>,

/// Features are a deprecated way to specify different feature sets for the
/// conda solver. This is not supported anymore and should not be used.
/// Instead, `mutex` packages should be used to specify
Expand Down
2 changes: 1 addition & 1 deletion crates/rattler_conda_types/src/repo_data/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -509,7 +509,7 @@ impl PackageRecord {
noarch: index.noarch,
platform: index.platform,
python_site_packages_path: index.python_site_packages_path,
extra_depends: BTreeMap::new(),
extra_depends: index.extras,
sha256,
size,
subdir,
Expand Down
2 changes: 1 addition & 1 deletion crates/rattler_index/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ pub fn package_record_from_index_json<T: Read>(
arch: index.arch,
platform: index.platform,
depends: index.depends,
extra_depends: std::collections::BTreeMap::new(),
extra_depends: index.extras,
constrains: index.constrains,
track_features: index.track_features,
features: index.features,
Expand Down
2 changes: 1 addition & 1 deletion crates/rattler_solve/src/resolvo/conda_sorting.rs
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ impl<'a, 'repo> SolvableSorter<'a, 'repo> {
};

for requirement in &known.requirements {
let version_set_id = match requirement {
let version_set_id = match &requirement.requirement {
// Ignore union requirements, these do not occur in the conda ecosystem
// currently
Requirement::Union(_) => {
Expand Down
Loading
Loading