Skip to content

Commit e2e9525

Browse files
olivier-lacroixtdejager
authored andcommitted
feat: Resolve optional dependencies recursively (prefix-dev#3646)
Co-authored-by: Tim de Jager <tdejager89@gmail.com>
1 parent 98791b8 commit e2e9525

File tree

8 files changed

+224
-335
lines changed

8 files changed

+224
-335
lines changed

Cargo.lock

Lines changed: 13 additions & 25 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ pep440_rs = "0.7.3"
7272
pep508_rs = "0.9.2"
7373
percent-encoding = "2.3.1"
7474
pin-project-lite = "0.2.16"
75-
pyproject-toml = "0.13.4"
75+
pyproject-toml = "0.13.6"
7676
rand = { version = "0.9.1", default-features = false }
7777
rayon = "1.10.0"
7878
regex = "1.11.1"

crates/pixi/tests/integration_rust/pypi_tests.rs

Lines changed: 94 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,104 @@
1-
use std::io::Write;
1+
use std::{fs::File, io::Write, str::FromStr};
22

3+
use pep508_rs::Requirement;
34
use rattler_conda_types::Platform;
45
use tempfile::tempdir;
56
use typed_path::Utf8TypedPath;
67

78
use crate::common::pypi_index::{Database as PyPIDatabase, PyPIPackage};
8-
use crate::common::{LockFileExt, PixiControl};
9+
use crate::common::{
10+
LockFileExt, PixiControl,
11+
package_database::{Package, PackageDatabase},
12+
};
913
use crate::setup_tracing;
10-
use std::fs::File;
14+
15+
/// This tests if we can resolve pyproject optional dependencies recursively
16+
/// before when running `pixi list -e all`, this would have not included numpy
17+
/// we are now explicitly testing that this works
18+
#[tokio::test]
19+
async fn pyproject_optional_dependencies_resolve_recursively() {
20+
setup_tracing();
21+
22+
let simple = PyPIDatabase::new()
23+
.with(PyPIPackage::new("numpy", "1.0.0"))
24+
.with(PyPIPackage::new("sphinx", "1.0.0"))
25+
.with(PyPIPackage::new("pytest", "1.0.0"))
26+
.into_simple_index()
27+
.unwrap();
28+
29+
let platform = Platform::current();
30+
let platform_str = platform.to_string();
31+
32+
let mut package_db = PackageDatabase::default();
33+
package_db.add_package(
34+
Package::build("python", "3.11.0")
35+
.with_subdir(platform)
36+
.finish(),
37+
);
38+
let channel = package_db.into_channel().await.unwrap();
39+
let channel_url = channel.url();
40+
let index_url = simple.index_url();
41+
42+
let pyproject = format!(
43+
r#"
44+
[build-system]
45+
requires = ["setuptools"]
46+
build-backend = "setuptools.build_meta"
47+
48+
[project]
49+
name = "recursive-optional-groups"
50+
51+
[project.optional-dependencies]
52+
np = ["numpy"]
53+
all = ["recursive-optional-groups[np]"]
54+
55+
[dependency-groups]
56+
docs = ["sphinx"]
57+
test = ["recursive-optional-groups[np]", "pytest", {{include-group = "docs"}}]
58+
59+
[tool.pixi.project]
60+
channels = ["{channel_url}"]
61+
platforms = ["{platform}"]
62+
63+
[tool.pixi.dependencies]
64+
python = "==3.11.0"
65+
66+
[tool.pixi.pypi-options]
67+
index-url = "{index_url}"
68+
69+
[tool.pixi.environments]
70+
np = {{features = ["np"]}}
71+
all = {{features = ["all"]}}
72+
test = {{features = ["test"]}}
73+
"#,
74+
platform = platform_str,
75+
channel_url = channel_url,
76+
index_url = index_url,
77+
);
78+
79+
let pixi = PixiControl::from_pyproject_manifest(&pyproject).unwrap();
80+
81+
let lock = pixi.update_lock_file().await.unwrap();
82+
83+
let numpy_req = Requirement::from_str("numpy").unwrap();
84+
let sphinx_req = Requirement::from_str("sphinx").unwrap();
85+
assert!(
86+
lock.contains_pep508_requirement("np", platform, numpy_req.clone()),
87+
"np environment should include numpy from optional dependencies"
88+
);
89+
assert!(
90+
lock.contains_pep508_requirement("all", platform, numpy_req.clone()),
91+
"all environment should include numpy inherited from recursive optional dependency"
92+
);
93+
assert!(
94+
lock.contains_pep508_requirement("test", platform, numpy_req),
95+
"test environment should include numpy inherited from recursive optional dependency"
96+
);
97+
assert!(
98+
lock.contains_pep508_requirement("test", platform, sphinx_req),
99+
"test environment should include sphinx inherited from recursive dependency group"
100+
);
101+
}
11102

12103
#[tokio::test]
13104
#[cfg_attr(not(feature = "slow_integration_tests"), ignore)]

crates/pixi_api/src/workspace/init/mod.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -152,10 +152,10 @@ pub async fn init<I: Interface>(interface: &I, options: InitOptions) -> miette::
152152
}
153153

154154
let (name, pixi_name) = match pyproject.name() {
155-
Some(name) => (name, false),
156-
None => (default_name.as_str(), true),
155+
Some(name) => (name.to_string(), false),
156+
None => (default_name.clone(), true),
157157
};
158-
let environments = pyproject.environments_from_extras().into_diagnostic()?;
158+
let environments = pyproject.environments_from_groups().into_diagnostic()?;
159159
let rv = env
160160
.render_named_str(
161161
consts::PYPROJECT_MANIFEST,

crates/pixi_manifest/src/error.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use itertools::Itertools;
99
use miette::{Diagnostic, LabeledSpan, SourceOffset, SourceSpan};
1010
use pixi_pypi_spec::Pep508ToPyPiRequirementError;
1111
use pixi_toml::TomlDiagnostic;
12+
use pyproject_toml::ResolveError;
1213
use rattler_conda_types::{InvalidPackageNameError, version_spec::ParseVersionSpecError};
1314
use thiserror::Error;
1415
use toml_span::{DeserError, Error};
@@ -113,6 +114,8 @@ pub enum TomlError {
113114
#[error(transparent)]
114115
Conversion(#[from] Box<Pep508ToPyPiRequirementError>),
115116
#[error(transparent)]
117+
ResolveError(#[from] ResolveError),
118+
#[error(transparent)]
116119
InvalidNonPackageDependencies(#[from] InvalidNonPackageDependencies),
117120
}
118121

@@ -163,6 +166,7 @@ impl Display for TomlError {
163166
write!(f, "Could not convert pep508 to pixi pypi requirement")
164167
}
165168
TomlError::InvalidNonPackageDependencies(err) => write!(f, "{err}"),
169+
TomlError::ResolveError(err) => write!(f, "{err}"),
166170
}
167171
}
168172
}

0 commit comments

Comments
 (0)