@@ -3,10 +3,13 @@ use crate::Metadata;
33
44use std:: collections:: BTreeMap ;
55use std:: path:: Path ;
6+ use std:: str:: FromStr ;
7+
68use uv_configuration:: { LowerBound , SourceStrategy } ;
79use uv_distribution_types:: IndexLocations ;
810use uv_normalize:: { ExtraName , GroupName , PackageName , DEV_DEPENDENCIES } ;
9- use uv_workspace:: pyproject:: ToolUvSources ;
11+ use uv_pypi_types:: VerbatimParsedUrl ;
12+ use uv_workspace:: pyproject:: { Sources , ToolUvSources } ;
1013use uv_workspace:: { DiscoveryOptions , ProjectWorkspace } ;
1114
1215#[ derive( Debug , Clone ) ]
@@ -97,48 +100,71 @@ impl RequiresDist {
97100 } ;
98101
99102 let dev_dependencies = {
103+ // First, collect `tool.uv.dev_dependencies`
100104 let dev_dependencies = project_workspace
101105 . current_project ( )
102106 . pyproject_toml ( )
103107 . tool
104108 . as_ref ( )
105109 . and_then ( |tool| tool. uv . as_ref ( ) )
106- . and_then ( |uv| uv. dev_dependencies . as_ref ( ) )
107- . into_iter ( )
110+ . and_then ( |uv| uv. dev_dependencies . as_ref ( ) ) ;
111+
112+ // Then, collect `dependency-groups`
113+ let dependency_groups = project_workspace
114+ . current_project ( )
115+ . pyproject_toml ( )
116+ . dependency_groups
117+ . iter ( )
108118 . flatten ( )
109- . cloned ( ) ;
110- let dev_dependencies = match source_strategy {
111- SourceStrategy :: Enabled => dev_dependencies
112- . flat_map ( |requirement| {
113- let requirement_name = requirement. name . clone ( ) ;
114- LoweredRequirement :: from_requirement (
115- requirement,
116- & metadata. name ,
117- project_workspace. project_root ( ) ,
119+ . map ( |( name, requirements) | {
120+ (
121+ name. clone ( ) ,
122+ requirements
123+ . iter ( )
124+ . map ( |requirement| {
125+ match uv_pep508:: Requirement :: < VerbatimParsedUrl > :: from_str (
126+ requirement,
127+ ) {
128+ Ok ( requirement) => Ok ( requirement) ,
129+ Err ( err) => Err ( MetadataError :: GroupParseError (
130+ name. clone ( ) ,
131+ requirement. clone ( ) ,
132+ Box :: new ( err) ,
133+ ) ) ,
134+ }
135+ } )
136+ . collect :: < Result < Vec < _ > , _ > > ( ) ,
137+ )
138+ } )
139+ . chain (
140+ // Only add the `dev` group if `dev-dependencies` is defined
141+ dev_dependencies
142+ . into_iter ( )
143+ . map ( |requirements| ( DEV_DEPENDENCIES . clone ( ) , Ok ( requirements. clone ( ) ) ) ) ,
144+ )
145+ . map ( |( name, requirements) | {
146+ // Apply sources to the requirements
147+ match requirements {
148+ Ok ( requirements) => match apply_source_strategy (
149+ source_strategy,
150+ requirements,
151+ & metadata,
118152 project_sources,
119153 project_indexes,
120154 locations,
121- project_workspace. workspace ( ) ,
155+ project_workspace,
122156 lower_bound,
123- )
124- . map ( move |requirement| match requirement {
125- Ok ( requirement) => Ok ( requirement. into_inner ( ) ) ,
126- Err ( err) => {
127- Err ( MetadataError :: LoweringError ( requirement_name. clone ( ) , err) )
128- }
129- } )
130- } )
131- . collect :: < Result < Vec < _ > , _ > > ( ) ?,
132- SourceStrategy :: Disabled => dev_dependencies
133- . into_iter ( )
134- . map ( uv_pypi_types:: Requirement :: from)
135- . collect ( ) ,
136- } ;
137- if dev_dependencies. is_empty ( ) {
138- BTreeMap :: default ( )
139- } else {
140- BTreeMap :: from ( [ ( DEV_DEPENDENCIES . clone ( ) , dev_dependencies) ] )
141- }
157+ & name,
158+ ) {
159+ Ok ( requirements) => Ok ( ( name, requirements) ) ,
160+ Err ( err) => Err ( err) ,
161+ } ,
162+ Err ( err) => Err ( err) ,
163+ }
164+ } )
165+ . collect :: < Result < Vec < _ > , _ > > ( ) ?;
166+
167+ dependency_groups. into_iter ( ) . collect :: < BTreeMap < _ , _ > > ( )
142168 } ;
143169
144170 let requires_dist = metadata. requires_dist . into_iter ( ) ;
@@ -158,9 +184,10 @@ impl RequiresDist {
158184 )
159185 . map ( move |requirement| match requirement {
160186 Ok ( requirement) => Ok ( requirement. into_inner ( ) ) ,
161- Err ( err) => {
162- Err ( MetadataError :: LoweringError ( requirement_name. clone ( ) , err) )
163- }
187+ Err ( err) => Err ( MetadataError :: LoweringError (
188+ requirement_name. clone ( ) ,
189+ Box :: new ( err) ,
190+ ) ) ,
164191 } )
165192 } )
166193 . collect :: < Result < Vec < _ > , _ > > ( ) ?,
@@ -190,6 +217,49 @@ impl From<Metadata> for RequiresDist {
190217 }
191218}
192219
220+ fn apply_source_strategy (
221+ source_strategy : SourceStrategy ,
222+ requirements : Vec < uv_pep508:: Requirement < VerbatimParsedUrl > > ,
223+ metadata : & uv_pypi_types:: RequiresDist ,
224+ project_sources : & BTreeMap < PackageName , Sources > ,
225+ project_indexes : & [ uv_distribution_types:: Index ] ,
226+ locations : & IndexLocations ,
227+ project_workspace : & ProjectWorkspace ,
228+ lower_bound : LowerBound ,
229+ group_name : & GroupName ,
230+ ) -> Result < Vec < uv_pypi_types:: Requirement > , MetadataError > {
231+ match source_strategy {
232+ SourceStrategy :: Enabled => requirements
233+ . into_iter ( )
234+ . flat_map ( |requirement| {
235+ let requirement_name = requirement. name . clone ( ) ;
236+ LoweredRequirement :: from_requirement (
237+ requirement,
238+ & metadata. name ,
239+ project_workspace. project_root ( ) ,
240+ project_sources,
241+ project_indexes,
242+ locations,
243+ project_workspace. workspace ( ) ,
244+ lower_bound,
245+ )
246+ . map ( move |requirement| match requirement {
247+ Ok ( requirement) => Ok ( requirement. into_inner ( ) ) ,
248+ Err ( err) => Err ( MetadataError :: GroupLoweringError (
249+ group_name. clone ( ) ,
250+ requirement_name. clone ( ) ,
251+ Box :: new ( err) ,
252+ ) ) ,
253+ } )
254+ } )
255+ . collect :: < Result < Vec < _ > , _ > > ( ) ,
256+ SourceStrategy :: Disabled => Ok ( requirements
257+ . into_iter ( )
258+ . map ( uv_pypi_types:: Requirement :: from)
259+ . collect ( ) ) ,
260+ }
261+ }
262+
193263#[ cfg( test) ]
194264mod test {
195265 use std:: path:: Path ;
@@ -255,7 +325,7 @@ mod test {
255325 "# } ;
256326
257327 assert_snapshot ! ( format_err( input) . await , @r###"
258- error: Failed to parse entry for : `tqdm`
328+ error: Failed to parse entry: `tqdm`
259329 Caused by: Can't combine URLs from both `project.dependencies` and `tool.uv.sources`
260330 "### ) ;
261331 }
@@ -422,7 +492,7 @@ mod test {
422492 "# } ;
423493
424494 assert_snapshot ! ( format_err( input) . await , @r###"
425- error: Failed to parse entry for : `tqdm`
495+ error: Failed to parse entry: `tqdm`
426496 Caused by: Can't combine URLs from both `project.dependencies` and `tool.uv.sources`
427497 "### ) ;
428498 }
@@ -441,7 +511,7 @@ mod test {
441511 "# } ;
442512
443513 assert_snapshot ! ( format_err( input) . await , @r###"
444- error: Failed to parse entry for : `tqdm`
514+ error: Failed to parse entry: `tqdm`
445515 Caused by: Package is not included as workspace package in `tool.uv.workspace`
446516 "### ) ;
447517 }
0 commit comments