1use std::collections::{BTreeMap, HashMap};
7
8use nova_diagnostics::{Diagnostic, Span};
9use semver::{Version, VersionReq};
10
11use crate::lock::{Lock, LockedPackage};
12
13#[derive(Debug, Clone, PartialEq, Eq)]
15pub struct Candidate {
16 pub version: Version,
17 pub deps: Vec<(String, VersionReq)>,
19 pub checksum: String,
21}
22
23pub trait IndexView {
25 fn versions(&mut self, name: &str) -> Result<Option<Vec<Candidate>>, String>;
28}
29
30#[derive(Debug, Clone)]
32pub struct Requirement {
33 pub name: String,
34 pub req: VersionReq,
35 pub by: String,
37 pub span: Span,
40}
41
42#[derive(Debug, Clone, PartialEq, Eq)]
44pub enum Unlock {
45 Nothing,
48 All,
50 One(String),
52}
53
54#[derive(Debug)]
56pub enum ResolveError {
57 Diagnostic(Diagnostic),
59 Index(String),
61}
62
63pub fn resolve(
67 requirements: &[Requirement],
68 lock: Option<&Lock>,
69 unlock: &Unlock,
70 index: &mut dyn IndexView,
71) -> Result<Vec<LockedPackage>, ResolveError> {
72 let pins = |except: Option<&str>| -> HashMap<String, Version> {
73 lock.map(|lock| {
74 lock.packages
75 .iter()
76 .filter(|p| Some(p.name.as_str()) != except)
77 .map(|p| (p.name.clone(), p.version.clone()))
78 .collect()
79 })
80 .unwrap_or_default()
81 };
82 let mut search = Search {
83 index,
84 known: HashMap::new(),
85 pinned: HashMap::new(),
86 strict: false,
87 failure: None,
88 conflicted: Vec::new(),
89 };
90 match unlock {
91 Unlock::All => search.run(requirements, HashMap::new(), false),
92 Unlock::One(name) => search
93 .run(requirements, pins(Some(name.as_str())), true)
94 .map_err(|error| match error {
95 ResolveError::Diagnostic(d) if d.code == "M0015" => ResolveError::Diagnostic(
96 d.with_note("the other locked versions were kept; run `nova update` to let them move too"),
97 ),
98 other => other,
99 }),
100 Unlock::Nothing => {
101 let mut pinned = pins(None);
106 loop {
107 match search.run(requirements, pinned.clone(), false) {
108 Err(ResolveError::Diagnostic(d)) if d.code == "M0015" && !pinned.is_empty() => {
109 let before = pinned.len();
110 for name in &search.conflicted {
111 pinned.remove(name);
112 }
113 if pinned.len() == before {
115 pinned.clear();
116 }
117 }
118 result => return result,
119 }
120 }
121 }
122 }
123}
124
125struct Failure {
127 name: String,
128 reqs: Vec<Requirement>,
129}
130
131struct State {
132 reqs: Vec<Requirement>,
133 order: Vec<String>,
135 chosen: BTreeMap<String, Candidate>,
136}
137
138struct Search<'a> {
139 index: &'a mut dyn IndexView,
140 known: HashMap<String, Vec<Candidate>>,
142 pinned: HashMap<String, Version>,
144 strict: bool,
147 failure: Option<Failure>,
149 conflicted: Vec<String>,
152}
153
154impl Search<'_> {
155 fn run(
156 &mut self,
157 requirements: &[Requirement],
158 pinned: HashMap<String, Version>,
159 strict: bool,
160 ) -> Result<Vec<LockedPackage>, ResolveError> {
161 self.pinned = pinned;
162 self.strict = strict;
163 self.failure = None;
164 let mut order: Vec<String> = requirements.iter().map(|r| r.name.clone()).collect();
165 order.sort();
166 order.dedup();
167 let mut state = State {
168 reqs: requirements.to_vec(),
169 order,
170 chosen: BTreeMap::new(),
171 };
172 if self.solve(&mut state)? {
173 return Ok(state
174 .chosen
175 .into_iter()
176 .map(|(name, candidate)| {
177 let mut dependencies: Vec<String> =
178 candidate.deps.iter().map(|(n, _)| n.clone()).collect();
179 dependencies.sort();
180 dependencies.dedup();
181 LockedPackage {
182 name,
183 version: candidate.version,
184 checksum: candidate.checksum,
185 dependencies,
186 }
187 })
188 .collect());
189 }
190 let failure = self.failure.take().expect("a failed search records why");
191 self.conflicted = std::iter::once(failure.name.clone())
192 .chain(failure.reqs.iter().map(|r| r.by.clone()))
193 .collect();
194 Err(ResolveError::Diagnostic(conflict(&failure)))
195 }
196
197 fn solve(&mut self, state: &mut State) -> Result<bool, ResolveError> {
199 let Some(name) = state
200 .order
201 .iter()
202 .find(|n| !state.chosen.contains_key(*n))
203 .cloned()
204 else {
205 return Ok(true);
206 };
207 let reqs: Vec<Requirement> = state
208 .reqs
209 .iter()
210 .filter(|r| r.name == name)
211 .cloned()
212 .collect();
213 let fitting: Vec<Candidate> = self
214 .versions(&name, &reqs)?
215 .into_iter()
216 .filter(|c| reqs.iter().all(|r| r.req.matches(&c.version)))
217 .collect();
218 let kept = self
219 .pinned
220 .get(&name)
221 .map(|version| fitting.iter().position(|c| &c.version == version));
222 let tried = match kept {
223 Some(Some(i)) => vec![fitting[i].clone()],
225 Some(None) if self.strict => Vec::new(),
226 _ => fitting,
227 };
228 let via = reqs[0].span;
229 for candidate in tried {
230 let reqs_before = state.reqs.len();
231 let order_before = state.order.len();
232 let mut new_names = Vec::new();
233 for (dep, req) in &candidate.deps {
234 state.reqs.push(Requirement {
235 name: dep.clone(),
236 req: req.clone(),
237 by: name.clone(),
238 span: via,
239 });
240 if !state.order.contains(dep) && !new_names.contains(dep) {
241 new_names.push(dep.clone());
242 }
243 }
244 new_names.sort();
245 state.order.extend(new_names);
246 let clash = candidate.deps.iter().find(|(dep, req)| {
248 state
249 .chosen
250 .get(dep)
251 .is_some_and(|chosen| !req.matches(&chosen.version))
252 });
253 match clash {
254 Some((dep, _)) => {
255 let on_dep = state
256 .reqs
257 .iter()
258 .filter(|r| &r.name == dep)
259 .cloned()
260 .collect();
261 self.note(dep, on_dep);
262 }
263 None => {
264 state.chosen.insert(name.clone(), candidate.clone());
265 if self.solve(state)? {
266 return Ok(true);
267 }
268 state.chosen.remove(&name);
269 }
270 }
271 state.order.truncate(order_before);
272 state.reqs.truncate(reqs_before);
273 }
274 self.note(&name, reqs);
275 Ok(false)
276 }
277
278 fn versions(
280 &mut self,
281 name: &str,
282 reqs: &[Requirement],
283 ) -> Result<Vec<Candidate>, ResolveError> {
284 if !self.known.contains_key(name) {
285 let Some(mut versions) = self.index.versions(name).map_err(ResolveError::Index)? else {
286 return Err(ResolveError::Diagnostic(
287 Diagnostic::error("M0014", format!("the index has no package `{name}`"))
288 .with_primary_label(reqs[0].span, "required here"),
289 ));
290 };
291 versions.sort_by(|a, b| b.version.cmp(&a.version));
292 self.known.insert(name.to_string(), versions);
293 }
294 Ok(self.known[name].clone())
295 }
296
297 fn note(&mut self, name: &str, reqs: Vec<Requirement>) {
299 if self.failure.is_none() {
300 self.failure = Some(Failure {
301 name: name.to_string(),
302 reqs,
303 });
304 }
305 }
306}
307
308fn conflict(failure: &Failure) -> Diagnostic {
310 let wants: Vec<String> = failure
311 .reqs
312 .iter()
313 .map(|r| format!("`{}` from `{}`", r.req, r.by))
314 .collect();
315 Diagnostic::error(
316 "M0015",
317 format!(
318 "no version of `{}` meets every requirement: {}",
319 failure.name,
320 wants.join(", ")
321 ),
322 )
323 .with_primary_label(failure.reqs[0].span, "required here")
324}