@@ -9,7 +9,7 @@ use miette::{Diagnostic, IntoDiagnostic, Report, WrapErr};
9
9
use pep440_rs:: { Version , VersionSpecifiers } ;
10
10
use pep508_rs:: Requirement ;
11
11
use pixi_spec:: PixiSpec ;
12
- use pyproject_toml:: { self , pep735_resolve :: Pep735Error , Contact } ;
12
+ use pyproject_toml:: { self , has_recursion :: RecursionResolutionError , Contact } ;
13
13
use rattler_conda_types:: { PackageName , ParseStrictness :: Lenient , VersionSpec } ;
14
14
use thiserror:: Error ;
15
15
use toml_span:: Spanned ;
@@ -22,7 +22,7 @@ use crate::{
22
22
error:: { DependencyError , GenericError } ,
23
23
manifests:: PackageManifest ,
24
24
toml:: {
25
- pyproject:: { TomlContact , TomlDependencyGroups , TomlProject } ,
25
+ pyproject:: { TomlContact , TomlDependencyGroups , TomlOptionalDependencies , TomlProject } ,
26
26
ExternalPackageProperties , ExternalWorkspaceProperties , FromTomlStr , PyProjectToml ,
27
27
TomlManifest ,
28
28
} ,
@@ -97,11 +97,6 @@ impl PyProjectManifest {
97
97
None
98
98
}
99
99
100
- /// Returns the project name as PEP508 name
101
- fn package_name ( & self ) -> Option < pep508_rs:: PackageName > {
102
- pep508_rs:: PackageName :: new ( self . name ( ) ?. to_string ( ) ) . ok ( )
103
- }
104
-
105
100
fn tool ( & self ) -> Option < & Tool > {
106
101
self . tool . as_ref ( )
107
102
}
@@ -124,19 +119,19 @@ impl PyProjectManifest {
124
119
125
120
/// Returns optional dependencies from the `[project.optional-dependencies]`
126
121
/// table
127
- fn optional_dependencies ( & self ) -> Option < IndexMap < String , Vec < Requirement > > > {
122
+ fn optional_dependencies (
123
+ & self ,
124
+ project_name : Option < & str > ,
125
+ ) -> Option < Result < IndexMap < String , Vec < Requirement > > , RecursionResolutionError > > {
128
126
let project = self . project . project . as_ref ( ) ?;
129
127
let optional_dependencies = project. optional_dependencies . as_ref ( ) ?;
130
- Some (
131
- optional_dependencies
132
- . iter ( )
133
- . map ( |( k, v) | ( k. clone ( ) , v. iter ( ) . cloned ( ) . map ( Spanned :: take) . collect ( ) ) )
134
- . collect ( ) ,
135
- )
128
+ Some ( optional_dependencies. value . 0 . resolve ( project_name) )
136
129
}
137
130
138
131
/// Returns dependency groups from the `[dependency-groups]` table
139
- fn dependency_groups ( & self ) -> Option < Result < IndexMap < String , Vec < Requirement > > , Pep735Error > > {
132
+ fn dependency_groups (
133
+ & self ,
134
+ ) -> Option < Result < IndexMap < String , Vec < Requirement > > , RecursionResolutionError > > {
140
135
let dg = self . project . dependency_groups . as_ref ( ) ?;
141
136
Some ( dg. value . 0 . resolve ( ) )
142
137
}
@@ -145,37 +140,25 @@ impl PyProjectManifest {
145
140
/// dependencies and/or dependency groups:
146
141
/// - one environment is created per group with the same name
147
142
/// - each environment includes the feature of the same name
148
- /// - it will also include other features inferred from any self references
149
- /// to other groups of optional dependencies (but won't for dependency
150
- /// groups, as recursion between groups is resolved upstream)
151
- pub fn environments_from_extras ( & self ) -> Result < HashMap < String , Vec < String > > , Pep735Error > {
143
+ pub fn environments_from_dependency_groups (
144
+ & self ,
145
+ ) -> Result < HashMap < String , Vec < String > > , RecursionResolutionError > {
152
146
let mut environments = HashMap :: new ( ) ;
153
- if let Some ( extras) = self . optional_dependencies ( ) {
154
- let pname = self . package_name ( ) ;
155
- for ( extra, reqs) in extras {
156
- let mut features = vec ! [ extra. to_string( ) ] ;
157
- // Add any references to other groups of extra dependencies
158
- for req in reqs. iter ( ) {
159
- if pname. as_ref ( ) == Some ( & req. name ) {
160
- for extra in & req. extras {
161
- features. push ( extra. to_string ( ) )
162
- }
163
- }
164
- }
165
- // Environments can only contain number, strings and dashes
166
- environments. insert ( extra. replace ( '_' , "-" ) . clone ( ) , features) ;
167
- }
168
- }
169
147
170
- if let Some ( groups) = self . dependency_groups ( ) . transpose ( ) ? {
171
- for group in groups. into_keys ( ) {
172
- let normalised = group. replace ( '_' , "-" ) ;
173
- // Nothing to do if a group of optional dependencies has the same name as the
174
- // dependency group
175
- if !environments. contains_key ( & normalised) {
176
- environments. insert ( normalised. clone ( ) , vec ! [ normalised] ) ;
177
- }
178
- }
148
+ let groups = self
149
+ // no need to pass project name to resolve recursions properly here,
150
+ // as only group names are used downstream
151
+ . optional_dependencies ( None )
152
+ . transpose ( ) ?
153
+ . unwrap_or_default ( )
154
+ . into_iter ( )
155
+ . chain ( self . dependency_groups ( ) . transpose ( ) ?. unwrap_or_default ( ) ) ;
156
+
157
+ for ( group, _) in groups {
158
+ let normalised = group. replace ( '_' , "-" ) ;
159
+ environments
160
+ . entry ( normalised. clone ( ) )
161
+ . or_insert_with ( || vec ! [ group] ) ;
179
162
}
180
163
181
164
Ok ( environments)
@@ -187,7 +170,7 @@ pub enum PyProjectToManifestError {
187
170
#[ error( "Unsupported pep508 requirement: '{0}'" ) ]
188
171
DependencyError ( Requirement , #[ source] DependencyError ) ,
189
172
#[ error( transparent) ]
190
- DependencyGroupError ( #[ from] Pep735Error ) ,
173
+ DependencyGroupError ( #[ from] RecursionResolutionError ) ,
191
174
#[ error( transparent) ]
192
175
TomlError ( #[ from] TomlError ) ,
193
176
}
@@ -200,7 +183,7 @@ pub struct PyProjectFields {
200
183
pub authors : Option < Vec < Spanned < TomlContact > > > ,
201
184
pub requires_python : Option < Spanned < VersionSpecifiers > > ,
202
185
pub dependencies : Option < Vec < Spanned < Requirement > > > ,
203
- pub optional_dependencies : Option < IndexMap < String , Vec < Spanned < Requirement > > > > ,
186
+ pub optional_dependencies : Option < Spanned < TomlOptionalDependencies > > ,
204
187
}
205
188
206
189
impl From < TomlProject > for PyProjectFields {
@@ -309,8 +292,12 @@ impl PyProjectManifest {
309
292
let poetry = poetry. unwrap_or_default ( ) ;
310
293
311
294
// Define an iterator over both optional dependencies and dependency groups
312
- let pypi_dependency_groups =
313
- Self :: extract_dependency_groups ( dependency_groups, project. optional_dependencies ) ?;
295
+ let project_name = project. name . map ( Spanned :: take) ;
296
+ let pypi_dependency_groups = Self :: extract_dependency_groups (
297
+ dependency_groups,
298
+ project. optional_dependencies ,
299
+ project_name. as_deref ( ) ,
300
+ ) ?;
314
301
315
302
// Convert the TOML document into a pixi manifest.
316
303
// TODO: would be nice to add license, license-file, readme, homepage,
@@ -329,7 +316,7 @@ impl PyProjectManifest {
329
316
. collect ( ) ;
330
317
let ( mut workspace_manifest, package_manifest, warnings) = pixi. into_workspace_manifest (
331
318
ExternalWorkspaceProperties {
332
- name : project . name . map ( Spanned :: take ) ,
319
+ name : project_name ,
333
320
version : project
334
321
. version
335
322
. and_then ( |v| v. take ( ) . to_string ( ) . parse ( ) . ok ( ) )
@@ -391,12 +378,7 @@ impl PyProjectManifest {
391
378
}
392
379
393
380
// For each group of optional dependency or dependency group, add pypi
394
- // dependencies, filtering out self-references in optional dependencies
395
- let project_name = workspace_manifest
396
- . workspace
397
- . name
398
- . clone ( )
399
- . and_then ( |name| pep508_rs:: PackageName :: new ( name) . ok ( ) ) ;
381
+ // dependencies
400
382
for ( group, reqs) in pypi_dependency_groups {
401
383
let feature_name = FeatureName :: from ( group. to_string ( ) ) ;
402
384
let target = workspace_manifest
@@ -406,16 +388,13 @@ impl PyProjectManifest {
406
388
. targets
407
389
. default_mut ( ) ;
408
390
for requirement in reqs. iter ( ) {
409
- // filter out any self references in groups of extra dependencies
410
- if project_name. as_ref ( ) != Some ( & requirement. name ) {
411
- target
412
- . try_add_pep508_dependency (
413
- requirement,
414
- None ,
415
- DependencyOverwriteBehavior :: Error ,
416
- )
417
- . map_err ( |err| GenericError :: new ( format ! ( "{}" , err) ) ) ?;
418
- }
391
+ target
392
+ . try_add_pep508_dependency (
393
+ requirement,
394
+ None ,
395
+ DependencyOverwriteBehavior :: Error ,
396
+ )
397
+ . map_err ( |err| GenericError :: new ( format ! ( "{}" , err) ) ) ?;
419
398
}
420
399
}
421
400
@@ -424,31 +403,28 @@ impl PyProjectManifest {
424
403
425
404
fn extract_dependency_groups (
426
405
dependency_groups : Option < Spanned < TomlDependencyGroups > > ,
427
- optional_dependencies : Option < IndexMap < String , Vec < Spanned < Requirement > > > > ,
406
+ optional_dependencies : Option < Spanned < TomlOptionalDependencies > > ,
407
+ project_name : Option < & str > ,
428
408
) -> Result < Vec < ( String , Vec < Requirement > ) > , TomlError > {
429
- Ok ( optional_dependencies
430
- . map ( |deps| {
431
- deps. into_iter ( )
432
- . map ( |( group, reqs) | {
433
- (
434
- group,
435
- reqs. into_iter ( ) . map ( Spanned :: take) . collect :: < Vec < _ > > ( ) ,
436
- )
437
- } )
438
- . collect ( )
439
- } )
440
- . into_iter ( )
441
- . chain (
442
- dependency_groups
443
- . map ( |Spanned { span, value } | {
444
- value. 0 . resolve ( ) . map_err ( |err| {
445
- GenericError :: new ( format ! ( "{}" , err) ) . with_span ( span. into ( ) )
446
- } )
447
- } )
448
- . transpose ( ) ?,
449
- )
450
- . flat_map ( |map| map. into_iter ( ) )
451
- . collect :: < Vec < _ > > ( ) )
409
+ let mut result = Vec :: new ( ) ;
410
+
411
+ if let Some ( Spanned { span, value } ) = optional_dependencies {
412
+ let resolved = value
413
+ . 0
414
+ . resolve ( project_name)
415
+ . map_err ( |err| GenericError :: new ( err. to_string ( ) ) . with_span ( span. into ( ) ) ) ?;
416
+ result. extend ( resolved) ;
417
+ }
418
+
419
+ if let Some ( Spanned { span, value } ) = dependency_groups {
420
+ let resolved = value
421
+ . 0
422
+ . resolve ( )
423
+ . map_err ( |err| GenericError :: new ( err. to_string ( ) ) . with_span ( span. into ( ) ) ) ?;
424
+ result. extend ( resolved) ;
425
+ }
426
+
427
+ Ok ( result)
452
428
}
453
429
}
454
430
0 commit comments