1use std::collections::hash_map::Entry;
2use std::collections::{HashMap, HashSet};
3use std::path::PathBuf;
4
5use indexmap::IndexMap;
6use semver::{Version, VersionReq};
7
8use super::compat::CompatibilityResult;
9use crate::config::{FilterMode, Manifest, SourceSpec};
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}
27
28#[derive(Debug, Clone)]
30pub struct ResolvedNode {
31 pub source_name: SourceName,
32 pub source_id: SourceId,
33 pub rooted_ref: RootedSourceRef,
34 pub resolved_ref: ResolvedRef,
35 pub latest_version: Option<Version>,
36 pub manifest: Option<Manifest>,
38 pub deps: Vec<SourceName>,
40}
41
42#[derive(Debug, Clone)]
44pub struct RootedSourceRef {
45 pub checkout_root: PathBuf,
46 pub package_root: PathBuf,
47}
48
49#[derive(Debug, Clone)]
51pub enum VersionConstraint {
52 Semver(VersionReq),
54 Latest,
56 RefPin(String),
58}
59
60impl std::fmt::Display for VersionConstraint {
61 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
62 match self {
63 VersionConstraint::Semver(req) => write!(f, "{req}"),
64 VersionConstraint::Latest => write!(f, "latest"),
65 VersionConstraint::RefPin(reference) => write!(f, "ref:{reference}"),
66 }
67 }
68}
69
70#[derive(Debug, Clone)]
72pub struct PendingItem {
73 pub package: SourceName,
75 pub item: ItemName,
77 pub kind: ItemKind,
79 pub constraint: VersionConstraint,
81 pub required_by: String,
83 pub is_local: bool,
85 pub spec: SourceSpec,
87}
88
89#[derive(Debug)]
91pub enum VersionCheckResult {
92 NotSeen,
94 SameVersion,
96 PotentiallyConflicting {
98 existing: VersionConstraint,
99 requested: VersionConstraint,
100 },
101 DifferentVersion {
103 existing: VersionConstraint,
104 requested: VersionConstraint,
105 },
106}
107
108#[derive(Debug, Clone, Hash, Eq, PartialEq)]
110struct VisitedItem {
111 package: SourceName,
112 item: ItemName,
113}
114
115#[derive(Debug, Clone)]
117pub struct ResolvedVersion {
118 pub constraint: VersionConstraint,
119 pub resolved_ref: ResolvedRef,
120}
121
122pub struct VisitedSet {
124 index: HashMap<(SourceName, ItemName), ResolvedVersion>,
126}
127
128impl Default for VisitedSet {
129 fn default() -> Self {
130 Self::new()
131 }
132}
133
134impl VisitedSet {
135 pub fn new() -> Self {
136 Self {
137 index: HashMap::new(),
138 }
139 }
140
141 fn index_key(package: &SourceName, item: &ItemName) -> (SourceName, ItemName) {
142 let key = VisitedItem {
143 package: package.clone(),
144 item: item.clone(),
145 };
146 (key.package, key.item)
147 }
148
149 pub fn check_version(
151 &self,
152 package: &SourceName,
153 item: &ItemName,
154 constraint: &VersionConstraint,
155 ) -> VersionCheckResult {
156 match self.index.get(&Self::index_key(package, item)) {
157 None => VersionCheckResult::NotSeen,
158 Some(existing) => match existing
159 .constraint
160 .compatible_with_resolved(constraint, existing.resolved_ref.version.as_ref())
161 {
162 CompatibilityResult::Compatible => VersionCheckResult::SameVersion,
163 CompatibilityResult::PotentiallyConflicting => {
164 VersionCheckResult::PotentiallyConflicting {
165 existing: existing.constraint.clone(),
166 requested: constraint.clone(),
167 }
168 }
169 CompatibilityResult::Conflicting => VersionCheckResult::DifferentVersion {
170 existing: existing.constraint.clone(),
171 requested: constraint.clone(),
172 },
173 },
174 }
175 }
176
177 pub fn insert(
179 &mut self,
180 package: SourceName,
181 item: ItemName,
182 constraint: VersionConstraint,
183 resolved_ref: ResolvedRef,
184 ) {
185 self.index.insert(
186 Self::index_key(&package, &item),
187 ResolvedVersion {
188 constraint,
189 resolved_ref,
190 },
191 );
192 }
193
194 pub fn iter(&self) -> impl Iterator<Item = (&(SourceName, ItemName), &ResolvedVersion)> {
196 self.index.iter()
197 }
198}
199
200pub struct PackageVersions {
202 versions: HashMap<SourceName, (ResolvedRef, VersionConstraint, String)>,
204}
205
206impl Default for PackageVersions {
207 fn default() -> Self {
208 Self::new()
209 }
210}
211
212impl PackageVersions {
213 pub fn new() -> Self {
214 Self {
215 versions: HashMap::new(),
216 }
217 }
218
219 pub fn check_or_insert(
221 &mut self,
222 package: &SourceName,
223 resolved: &ResolvedRef,
224 requested: &VersionConstraint,
225 required_by: &str,
226 is_local: bool,
227 ) -> Result<(), ResolutionError> {
228 if is_local {
229 return Ok(());
230 }
231
232 match self.versions.entry(package.clone()) {
233 Entry::Vacant(entry) => {
234 entry.insert((resolved.clone(), requested.clone(), required_by.to_string()));
235 Ok(())
236 }
237 Entry::Occupied(entry) => {
238 let (existing_ref, existing_constraint, existing_by) = entry.get();
239 match existing_constraint.compatible_with_resolved(
240 requested,
241 existing_ref.version.as_ref().or(resolved.version.as_ref()),
242 ) {
243 CompatibilityResult::Compatible
244 | CompatibilityResult::PotentiallyConflicting => {
245 if resolved_ref_matches(existing_ref, resolved) {
246 Ok(())
247 } else {
248 Err(ResolutionError::PackageVersionConflict {
249 package: package.to_string(),
250 existing: format!("{existing_ref:?} (required by {existing_by})"),
251 requested: format!("{resolved:?} (required by {required_by})"),
252 chain: required_by.to_string(),
253 })
254 }
255 }
256 CompatibilityResult::Conflicting => {
257 Err(ResolutionError::PackageVersionConflict {
258 package: package.to_string(),
259 existing: format!("{existing_constraint} (required by {existing_by})"),
260 requested: format!("{requested} (required by {required_by})"),
261 chain: required_by.to_string(),
262 })
263 }
264 }
265 }
266 }
267 }
268}
269
270fn resolved_ref_matches(existing: &ResolvedRef, incoming: &ResolvedRef) -> bool {
271 existing.source_name == incoming.source_name
272 && existing.version == incoming.version
273 && existing.version_tag == incoming.version_tag
274 && existing.commit == incoming.commit
275 && existing.tree_path == incoming.tree_path
276}
277
278#[derive(Debug, Clone, Default)]
280pub struct ResolveOptions {
281 pub maximize: bool,
283 pub upgrade_targets: HashSet<SourceName>,
285 pub bump_direct_constraints: bool,
288 pub frozen: bool,
290}