Skip to content

Commit 997e386

Browse files
committed
wip: resolve dev sources
1 parent b494101 commit 997e386

File tree

4 files changed

+268
-36
lines changed

4 files changed

+268
-36
lines changed

crates/pixi_command_dispatcher/src/solve_conda/mod.rs

Lines changed: 117 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use pixi_record::{PixiRecord, SourceRecord};
66
use pixi_spec::{BinarySpec, SourceSpec};
77
use pixi_spec_containers::DependencyMap;
88
use rattler_conda_types::{
9-
ChannelConfig, ChannelUrl, GenericVirtualPackage, MatchSpec, Platform, RepoDataRecord,
9+
ChannelConfig, ChannelUrl, GenericVirtualPackage, MatchSpec, Platform, RepoDataRecord, Version,
1010
};
1111
use rattler_repodata_gateway::RepoData;
1212
use 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)]
239345
pub enum SolveCondaEnvironmentError {
240346
#[error(transparent)]

0 commit comments

Comments
 (0)