1use std::collections::hash_map::Entry;
2use std::collections::{HashMap, HashSet};
3use std::path::PathBuf;
4
5use indexmap::IndexMap;
6use semver::VersionReq;
7
8use super::compat::CompatibilityResult;
9use crate::config::{FilterMode, Manifest};
10use crate::error::ResolutionError;
11use crate::lock::ItemKind;
12use crate::source::ResolvedRef;
13use crate::types::{ItemName, SourceId, SourceName};
14
15#[derive(Debug, Clone)]
20pub struct ResolvedGraph {
21 pub nodes: IndexMap<SourceName, ResolvedNode>,
22 pub order: Vec<SourceName>,
24 pub filters: HashMap<SourceName, Vec<FilterMode>>,
26 pub version_constraints: HashMap<SourceName, Vec<(String, VersionConstraint)>>,
28 pub unreadable_hook_surfaces:
33 std::collections::BTreeMap<SourceName, std::collections::BTreeSet<String>>,
34}
35
36#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]
37pub struct EngineFallbackSkippedVersion {
38 pub version: String,
39 pub requirements: Vec<EngineFallbackRequirement>,
40}
41
42#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]
43pub struct EngineFallbackRequirement {
44 pub engine: String,
45 pub requirement: String,
46}
47
48#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]
49pub struct EngineFallback {
50 pub source: String,
51 pub skipped: Vec<EngineFallbackSkippedVersion>,
52 pub selected_version: String,
53 pub engines: Vec<String>,
54}
55
56#[derive(Debug, Clone)]
58pub struct ResolvedNode {
59 pub source_name: SourceName,
60 pub source_id: SourceId,
61 pub rooted_ref: RootedSourceRef,
62 pub resolved_ref: ResolvedRef,
63 pub manifest: Option<Manifest>,
65 pub deps: Vec<SourceName>,
67}
68
69#[derive(Debug, Clone)]
71pub struct RootedSourceRef {
72 pub checkout_root: PathBuf,
73 pub package_root: PathBuf,
74}
75
76#[derive(Debug, Clone)]
78pub enum VersionConstraint {
79 Semver(VersionReq),
81 Latest,
83 RefPin(String),
85}
86
87impl std::fmt::Display for VersionConstraint {
88 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
89 match self {
90 VersionConstraint::Semver(req) => write!(f, "{req}"),
91 VersionConstraint::Latest => write!(f, "latest"),
92 VersionConstraint::RefPin(reference) => write!(f, "ref:{reference}"),
93 }
94 }
95}
96
97#[derive(Debug, Clone)]
99pub struct PendingItem {
100 pub package: SourceName,
102 pub item: ItemName,
104 pub kind: ItemKind,
106 pub constraint: VersionConstraint,
108 pub required_by: String,
110 pub is_local: bool,
112}
113
114#[derive(Debug)]
116pub enum VersionCheckResult {
117 NotSeen,
119 SameVersion,
121 PotentiallyConflicting {
123 existing: VersionConstraint,
124 requested: VersionConstraint,
125 },
126 DifferentVersion {
128 existing: VersionConstraint,
129 requested: VersionConstraint,
130 },
131}
132
133#[derive(Debug, Clone, Hash, Eq, PartialEq)]
135struct VisitedItem {
136 package: SourceName,
137 item: ItemName,
138}
139
140#[derive(Debug, Clone)]
142pub struct ResolvedVersion {
143 pub constraint: VersionConstraint,
144 pub resolved_ref: ResolvedRef,
145}
146
147pub struct VisitedSet {
149 index: HashMap<(SourceName, ItemName), ResolvedVersion>,
151}
152
153impl Default for VisitedSet {
154 fn default() -> Self {
155 Self::new()
156 }
157}
158
159impl VisitedSet {
160 pub fn new() -> Self {
161 Self {
162 index: HashMap::new(),
163 }
164 }
165
166 fn index_key(package: &SourceName, item: &ItemName) -> (SourceName, ItemName) {
167 let key = VisitedItem {
168 package: package.clone(),
169 item: item.clone(),
170 };
171 (key.package, key.item)
172 }
173
174 pub fn check_version(
176 &self,
177 package: &SourceName,
178 item: &ItemName,
179 constraint: &VersionConstraint,
180 ) -> VersionCheckResult {
181 match self.index.get(&Self::index_key(package, item)) {
182 None => VersionCheckResult::NotSeen,
183 Some(existing) => match existing
184 .constraint
185 .compatible_with_resolved(constraint, existing.resolved_ref.version.as_ref())
186 {
187 CompatibilityResult::Compatible => VersionCheckResult::SameVersion,
188 CompatibilityResult::PotentiallyConflicting => {
189 VersionCheckResult::PotentiallyConflicting {
190 existing: existing.constraint.clone(),
191 requested: constraint.clone(),
192 }
193 }
194 CompatibilityResult::Conflicting => VersionCheckResult::DifferentVersion {
195 existing: existing.constraint.clone(),
196 requested: constraint.clone(),
197 },
198 },
199 }
200 }
201
202 pub fn insert(
204 &mut self,
205 package: SourceName,
206 item: ItemName,
207 constraint: VersionConstraint,
208 resolved_ref: ResolvedRef,
209 ) {
210 self.index.insert(
211 Self::index_key(&package, &item),
212 ResolvedVersion {
213 constraint,
214 resolved_ref,
215 },
216 );
217 }
218}
219
220pub struct PackageVersions {
222 versions: HashMap<SourceName, (ResolvedRef, VersionConstraint, String)>,
224}
225
226impl Default for PackageVersions {
227 fn default() -> Self {
228 Self::new()
229 }
230}
231
232impl PackageVersions {
233 pub fn new() -> Self {
234 Self {
235 versions: HashMap::new(),
236 }
237 }
238
239 pub fn check_or_insert(
241 &mut self,
242 package: &SourceName,
243 resolved: &ResolvedRef,
244 requested: &VersionConstraint,
245 required_by: &str,
246 is_local: bool,
247 ) -> Result<(), ResolutionError> {
248 if is_local {
249 return Ok(());
250 }
251
252 match self.versions.entry(package.clone()) {
253 Entry::Vacant(entry) => {
254 entry.insert((resolved.clone(), requested.clone(), required_by.to_string()));
255 Ok(())
256 }
257 Entry::Occupied(entry) => {
258 let (existing_ref, existing_constraint, existing_by) = entry.get();
259 match existing_constraint.compatible_with_resolved(
260 requested,
261 existing_ref.version.as_ref().or(resolved.version.as_ref()),
262 ) {
263 CompatibilityResult::Compatible
264 | CompatibilityResult::PotentiallyConflicting => {
265 if resolved_ref_matches(existing_ref, resolved) {
266 Ok(())
267 } else {
268 Err(ResolutionError::PackageVersionConflict {
269 package: package.to_string(),
270 existing: format!("{existing_ref:?} (required by {existing_by})"),
271 requested: format!("{resolved:?} (required by {required_by})"),
272 chain: required_by.to_string(),
273 })
274 }
275 }
276 CompatibilityResult::Conflicting => {
277 Err(ResolutionError::PackageVersionConflict {
278 package: package.to_string(),
279 existing: format!("{existing_constraint} (required by {existing_by})"),
280 requested: format!("{requested} (required by {required_by})"),
281 chain: required_by.to_string(),
282 })
283 }
284 }
285 }
286 }
287 }
288}
289
290fn resolved_ref_matches(existing: &ResolvedRef, incoming: &ResolvedRef) -> bool {
291 existing.source_name == incoming.source_name
292 && existing.version == incoming.version
293 && existing.version_tag == incoming.version_tag
294 && existing.commit == incoming.commit
295 && crate::target::paths_equivalent(
296 &existing.tree_path.to_string_lossy(),
297 &incoming.tree_path.to_string_lossy(),
298 )
299}
300
301#[derive(Debug, Clone, PartialEq, Eq)]
303pub enum ResolveMode {
304 Sync,
306 Frozen,
308 Upgrade {
310 targets: HashSet<SourceName>,
312 bump_direct_constraints: bool,
314 },
315}
316
317#[derive(Debug, Clone, PartialEq, Eq)]
319pub struct ResolveOptions {
320 pub mode: ResolveMode,
321 pub mars_version: Option<semver::Version>,
323 pub meridian_version: Option<semver::Version>,
325 pub ignore_requires_mars: bool,
326 pub ignore_requires_meridian: bool,
327 pub staging_root: Option<std::path::PathBuf>,
329 pub(crate) source_overrides: indexmap::IndexMap<SourceName, std::path::PathBuf>,
331}
332
333impl Default for ResolveOptions {
334 fn default() -> Self {
335 Self {
336 mode: ResolveMode::Sync,
337 mars_version: None,
338 meridian_version: None,
339 ignore_requires_mars: false,
340 ignore_requires_meridian: false,
341 staging_root: None,
342 source_overrides: indexmap::IndexMap::new(),
343 }
344 }
345}
346
347#[derive(Debug, Clone, Copy, PartialEq, Eq)]
349pub(crate) enum VersionSelectionPolicy {
350 PreferLockThenLatest,
352 LatestOnly,
354 LockOnly,
356}
357
358impl ResolveOptions {
359 pub fn sync() -> Self {
360 Self {
361 mode: ResolveMode::Sync,
362 mars_version: None,
363 meridian_version: None,
364 ignore_requires_mars: false,
365 ignore_requires_meridian: false,
366 staging_root: None,
367 source_overrides: indexmap::IndexMap::new(),
368 }
369 }
370
371 pub fn frozen() -> Self {
372 Self {
373 mode: ResolveMode::Frozen,
374 mars_version: None,
375 meridian_version: None,
376 ignore_requires_mars: false,
377 ignore_requires_meridian: false,
378 staging_root: None,
379 source_overrides: indexmap::IndexMap::new(),
380 }
381 }
382
383 pub fn upgrade(targets: HashSet<SourceName>, bump_direct_constraints: bool) -> Self {
384 Self {
385 mode: ResolveMode::Upgrade {
386 targets,
387 bump_direct_constraints,
388 },
389 mars_version: None,
390 meridian_version: None,
391 ignore_requires_mars: false,
392 ignore_requires_meridian: false,
393 staging_root: None,
394 source_overrides: indexmap::IndexMap::new(),
395 }
396 }
397
398 pub fn with_staging_root(mut self, staging_root: std::path::PathBuf) -> Self {
399 self.staging_root = Some(staging_root);
400 self
401 }
402
403 pub(crate) fn with_source_overrides(
404 mut self,
405 source_overrides: indexmap::IndexMap<SourceName, std::path::PathBuf>,
406 ) -> Self {
407 self.source_overrides = source_overrides;
408 self
409 }
410
411 pub(crate) fn direct_constraint_for(
412 &self,
413 source_name: &SourceName,
414 declared: VersionConstraint,
415 ) -> VersionConstraint {
416 if matches!(
417 &self.mode,
418 ResolveMode::Upgrade {
419 bump_direct_constraints: true,
420 ..
421 }
422 ) && self.is_upgrade_target(source_name)
423 {
424 VersionConstraint::Latest
425 } else {
426 declared
427 }
428 }
429
430 pub(crate) fn is_upgrade_target(&self, source_name: &SourceName) -> bool {
431 match &self.mode {
432 ResolveMode::Upgrade { targets, .. } => {
433 targets.is_empty() || targets.contains(source_name)
434 }
435 ResolveMode::Sync | ResolveMode::Frozen => false,
436 }
437 }
438
439 pub(crate) fn version_selection_policy(
440 &self,
441 source_name: &SourceName,
442 ) -> VersionSelectionPolicy {
443 match &self.mode {
444 ResolveMode::Frozen => VersionSelectionPolicy::LockOnly,
445 ResolveMode::Upgrade { .. } if self.is_upgrade_target(source_name) => {
446 VersionSelectionPolicy::LatestOnly
447 }
448 ResolveMode::Sync | ResolveMode::Upgrade { .. } => {
449 VersionSelectionPolicy::PreferLockThenLatest
450 }
451 }
452 }
453}