@@ -6,7 +6,7 @@ use pixi_record::{PixiRecord, SourceRecord};
66use pixi_spec:: { BinarySpec , SourceSpec } ;
77use pixi_spec_containers:: DependencyMap ;
88use rattler_conda_types:: {
9- ChannelConfig , ChannelUrl , GenericVirtualPackage , MatchSpec , Platform , RepoDataRecord ,
9+ ChannelConfig , ChannelUrl , GenericVirtualPackage , MatchSpec , Platform , RepoDataRecord , Version ,
1010} ;
1111use rattler_repodata_gateway:: RepoData ;
1212use rattler_solve:: { ChannelPriority , SolveStrategy , SolverImpl } ;
@@ -41,6 +41,10 @@ pub struct SolveCondaEnvironmentSpec {
4141 #[ serde( skip_serializing_if = "DependencyMap::is_empty" ) ]
4242 pub constraints : DependencyMap < rattler_conda_types:: PackageName , BinarySpec > ,
4343
44+ /// Development source records whose dependencies should be installed.
45+ #[ serde( skip) ]
46+ pub dev_source_records : Vec < pixi_record:: DevSourceRecord > ,
47+
4448 /// Available source repodata records.
4549 #[ serde( skip) ]
4650 pub source_repodata : Vec < Arc < SourceMetadata > > ,
@@ -87,6 +91,7 @@ impl Default for SolveCondaEnvironmentSpec {
8791 source_specs : DependencyMap :: default ( ) ,
8892 binary_specs : DependencyMap :: default ( ) ,
8993 constraints : DependencyMap :: default ( ) ,
94+ dev_source_records : vec ! [ ] ,
9095 source_repodata : vec ! [ ] ,
9196 binary_repodata : vec ! [ ] ,
9297 installed : vec ! [ ] ,
@@ -138,9 +143,32 @@ impl SolveCondaEnvironmentSpec {
138143 . into_match_specs ( & self . channel_config )
139144 . map_err ( SolveCondaEnvironmentError :: SpecConversionError ) ?;
140145
141- // Construct repodata records for source records so that we can feed them to the
146+ // Create match specs for dev source packages themselves
147+ // Use a special prefix to avoid name clashes with real packages
148+ // TODO: It would be nicer if the rattler solver could handle this directly
149+ // by introducing a special type of name/package for these virtual dependencies
150+ // that represent "install my dependencies but not me" packages.
151+ let dev_source_match_specs = self
152+ . dev_source_records
153+ . iter ( )
154+ . map ( |dev_source| {
155+ let prefixed_name =
156+ format ! ( "__pixi_dev_source_{}" , dev_source. name. as_normalized( ) ) ;
157+ MatchSpec {
158+ name : Some ( rattler_conda_types:: PackageName :: new_unchecked (
159+ prefixed_name,
160+ ) ) ,
161+ ..MatchSpec :: default ( )
162+ }
163+ } )
164+ . collect :: < Vec < _ > > ( ) ;
165+
166+ // Construct repodata records for source records and dev sources so that we can feed them to the
142167 // solver.
143168 let mut url_to_source_package = HashMap :: new ( ) ;
169+ let mut url_to_dev_source = HashMap :: new ( ) ;
170+
171+ // Add source records
144172 for source_metadata in & self . source_repodata {
145173 for record in & source_metadata. records {
146174 let url = unique_url ( record) ;
@@ -159,8 +187,63 @@ impl SolveCondaEnvironmentSpec {
159187 }
160188 }
161189
162- // Collect repodata records from the remote servers and from the source metadata
163- // together. The repodata records go into the first "channel" to ensure
190+ // Collect all dev source names for filtering
191+ let dev_source_names: std:: collections:: HashSet < _ > = self
192+ . dev_source_records
193+ . iter ( )
194+ . map ( |ds| ds. name . clone ( ) )
195+ . collect ( ) ;
196+
197+ // Add dev source records
198+ for dev_source in & self . dev_source_records {
199+ let url = unique_dev_source_url ( dev_source) ;
200+ let prefixed_name =
201+ format ! ( "__pixi_dev_source_{}" , dev_source. name. as_normalized( ) ) ;
202+ let repodata_record = RepoDataRecord {
203+ package_record : rattler_conda_types:: PackageRecord {
204+ subdir : self . platform . to_string ( ) ,
205+ depends : dev_source
206+ . dependencies
207+ . iter_specs ( )
208+ . filter ( |( name, _) | !dev_source_names. contains ( * name) )
209+ . map ( |( name, spec) | {
210+ let nameless = spec
211+ . clone ( )
212+ . try_into_nameless_match_spec_ref ( & self . channel_config )
213+ . unwrap_or_default ( ) ;
214+ MatchSpec :: from_nameless ( nameless, Some ( name. clone ( ) ) ) . to_string ( )
215+ } )
216+ . collect ( ) ,
217+ constrains : dev_source
218+ . constraints
219+ . iter_specs ( )
220+ . filter ( |( name, _) | !dev_source_names. contains ( * name) )
221+ . filter_map ( |( name, spec) | {
222+ let nameless = spec
223+ . clone ( )
224+ . try_into_nameless_match_spec ( & self . channel_config )
225+ . ok ( ) ?;
226+ Some (
227+ MatchSpec :: from_nameless ( nameless, Some ( name. clone ( ) ) )
228+ . to_string ( ) ,
229+ )
230+ } )
231+ . collect ( ) ,
232+ ..rattler_conda_types:: PackageRecord :: new (
233+ rattler_conda_types:: PackageName :: new_unchecked ( prefixed_name. clone ( ) ) ,
234+ Version :: major ( 0 ) ,
235+ "dev" . to_owned ( ) ,
236+ )
237+ } ,
238+ url : url. clone ( ) ,
239+ file_name : format ! ( "{}-0-dev.devsource" , prefixed_name) ,
240+ channel : None ,
241+ } ;
242+ url_to_dev_source. insert ( url, ( dev_source, repodata_record) ) ;
243+ }
244+
245+ // Collect repodata records from the remote servers, source metadata, and dev sources
246+ // together. The source and dev source records go into the first "channel" to ensure
164247 // they are picked first.
165248 //
166249 // TODO: This only holds up when the channel priority is strict. We should
@@ -170,6 +253,7 @@ impl SolveCondaEnvironmentSpec {
170253 url_to_source_package
171254 . values ( )
172255 . map ( |( _, record) | record)
256+ . chain ( url_to_dev_source. values ( ) . map ( |( _, record) | record) )
173257 . collect_vec ( ) ,
174258 ) ;
175259 for repo_data in & self . binary_repodata {
@@ -181,6 +265,7 @@ impl SolveCondaEnvironmentSpec {
181265 specs : source_match_specs
182266 . into_iter ( )
183267 . chain ( binary_match_specs)
268+ . chain ( dev_source_match_specs)
184269 . collect ( ) ,
185270 locked_packages : installed,
186271 virtual_packages : self . virtual_packages ,
@@ -198,13 +283,17 @@ impl SolveCondaEnvironmentSpec {
198283 solver_result
199284 . records
200285 . into_iter ( )
201- . map ( |record| {
202- url_to_source_package. remove ( & record. url ) . map_or_else (
203- || PixiRecord :: Binary ( record) ,
204- |( source_record, _repodata_record) | {
205- PixiRecord :: Source ( source_record. clone ( ) )
206- } ,
207- )
286+ . filter_map ( |record| {
287+ if let Some ( source_record) = url_to_source_package. remove ( & record. url ) {
288+ // This is a source package, we want to return the source record
289+ // instead of the binary record.
290+ return Some ( PixiRecord :: Source ( source_record. 0 . clone ( ) ) ) ;
291+ } else if let Some ( _dev_source) = url_to_dev_source. remove ( & record. url ) {
292+ // This is a dev source, we don't want to return it.
293+ return None ;
294+ }
295+
296+ Some ( PixiRecord :: Binary ( record) )
208297 } )
209298 . collect_vec ( ) ,
210299 )
@@ -235,6 +324,23 @@ fn unique_url(source: &SourceRecord) -> Url {
235324 url
236325}
237326
327+ /// Generates a unique URL for a dev source record.
328+ fn unique_dev_source_url ( dev_source : & pixi_record:: DevSourceRecord ) -> Url {
329+ let mut url = dev_source. source . identifiable_url ( ) ;
330+
331+ // Add unique identifiers to the URL.
332+ let mut pairs = url. query_pairs_mut ( ) ;
333+ pairs. append_pair ( "name" , dev_source. name . as_source ( ) ) ;
334+
335+ for ( key, value) in & dev_source. variants {
336+ pairs. append_pair ( & format ! ( "_{}" , key) , & value) ;
337+ }
338+
339+ drop ( pairs) ;
340+
341+ url
342+ }
343+
238344#[ derive( Debug , thiserror:: Error ) ]
239345pub enum SolveCondaEnvironmentError {
240346 #[ error( transparent) ]
0 commit comments