1use std::cmp::Ordering;
2use std::collections::{BTreeMap, BTreeSet, VecDeque};
3
4use anyhow::bail;
5use anyhow::format_err;
6use semver::Version;
7use serde::Serialize;
8
9use crate::manifest::{Manifest, Realm};
10use crate::package_id::PackageId;
11use crate::package_req::PackageReq;
12use crate::package_source::{PackageSourceId, PackageSourceMap, PackageSourceProvider};
13
14#[derive(Debug, Default, Serialize, Clone)]
19pub struct Resolve {
20 pub activated: BTreeSet<PackageId>,
23
24 pub metadata: BTreeMap<PackageId, ResolvePackageMetadata>,
27
28 pub shared_dependencies: BTreeMap<PackageId, BTreeMap<String, PackageId>>,
30
31 pub server_dependencies: BTreeMap<PackageId, BTreeMap<String, PackageId>>,
33
34 pub dev_dependencies: BTreeMap<PackageId, BTreeMap<String, PackageId>>,
36}
37
38impl Resolve {
39 fn activate(&mut self, source: PackageId, dep_name: String, dep_realm: Realm, dep: PackageId) {
40 self.activated.insert(dep.clone());
41
42 let dependencies = match dep_realm {
43 Realm::Shared => self.shared_dependencies.entry(source).or_default(),
44 Realm::Server => self.server_dependencies.entry(source).or_default(),
45 Realm::Dev => self.dev_dependencies.entry(source).or_default(),
46 };
47 dependencies.insert(dep_name, dep);
48 }
49}
50
51#[derive(Debug, Serialize, Clone)]
56pub struct ResolvePackageMetadata {
57 pub realm: Realm,
58 pub origin_realm: Realm,
59 pub source_registry: PackageSourceId,
60}
61
62pub fn resolve(
63 root_manifest: &Manifest,
64 try_to_use: &BTreeSet<PackageId>,
65 package_sources: &PackageSourceMap,
66) -> anyhow::Result<Resolve> {
67 let mut resolve = Resolve::default();
68
69 resolve.activated.insert(root_manifest.package_id());
72 resolve.metadata.insert(
73 root_manifest.package_id(),
74 ResolvePackageMetadata {
75 realm: root_manifest.package.realm,
76 origin_realm: root_manifest.package.realm,
77 source_registry: PackageSourceId::DefaultRegistry,
78 },
79 );
80
81 let mut packages_to_visit = VecDeque::new();
83
84 for (alias, req) in &root_manifest.dependencies {
85 packages_to_visit.push_back(DependencyRequest {
86 request_source: root_manifest.package_id(),
87 request_realm: Realm::Shared,
88 origin_realm: Realm::Shared,
89 package_alias: alias.clone(),
90 package_req: req.clone(),
91 });
92 }
93
94 for (alias, req) in &root_manifest.server_dependencies {
95 packages_to_visit.push_back(DependencyRequest {
96 request_source: root_manifest.package_id(),
97 request_realm: Realm::Server,
98 origin_realm: Realm::Server,
99 package_alias: alias.clone(),
100 package_req: req.clone(),
101 });
102 }
103
104 for (alias, req) in &root_manifest.dev_dependencies {
105 packages_to_visit.push_back(DependencyRequest {
106 request_source: root_manifest.package_id(),
107 request_realm: Realm::Dev,
108 origin_realm: Realm::Dev,
109 package_alias: alias.clone(),
110 package_req: req.clone(),
111 });
112 }
113
114 'outer: while let Some(dependency_request) = packages_to_visit.pop_front() {
116 let mut matching_activated: Vec<_> = resolve
119 .activated
120 .iter()
121 .filter(|package_id| package_id.name() == dependency_request.package_req.name())
122 .cloned()
123 .collect();
124
125 matching_activated.sort_by(|a, b| b.version().cmp(a.version()));
128
129 for package_id in &matching_activated {
132 if dependency_request.package_req.matches_id(package_id) {
133 let metadata = resolve
134 .metadata
135 .get_mut(package_id)
136 .expect("activated package was missing metadata");
137
138 let realm_match = match (metadata.origin_realm, dependency_request.origin_realm) {
147 (_, Realm::Shared) => Realm::Shared,
148 (Realm::Shared, _) => Realm::Shared,
149 (_, Realm::Server) => Realm::Server,
150 (Realm::Server, _) => Realm::Server,
151 (Realm::Dev, Realm::Dev) => Realm::Dev,
152 };
153
154 metadata.origin_realm = realm_match;
155
156 resolve.activate(
157 dependency_request.request_source.clone(),
158 dependency_request.package_alias.clone(),
159 realm_match,
160 package_id.clone(),
161 );
162
163 continue 'outer;
164 }
165 }
166
167 let (source_registry, mut candidates) = package_sources
169 .source_order()
170 .iter()
171 .find_map(|source| {
172 let registry = package_sources.get(source).unwrap();
173
174 match registry.query(&dependency_request.package_req) {
177 Ok(manifests) => Some((source, manifests)),
178 Err(_) => None,
179 }
180 })
181 .ok_or_else(|| {
182 format_err!(
183 "Failed to find a source for {}",
184 dependency_request.package_req
185 )
186 })?;
187
188 candidates.sort_by(|a, b| {
195 let contains_a = try_to_use.contains(&a.package_id());
196 let contains_b = try_to_use.contains(&b.package_id());
197
198 match (contains_a, contains_b) {
199 (true, false) => Ordering::Less,
200 (false, true) => Ordering::Greater,
201 _ => b.package.version.cmp(&a.package.version),
202 }
203 });
204
205 let filtered_candidates = candidates.iter().filter(|candidate| {
206 Realm::is_dependency_valid(dependency_request.request_realm, candidate.package.realm)
207 });
208
209 let mut conflicting = Vec::new();
210
211 for candidate in filtered_candidates {
212 let has_conflicting = matching_activated
217 .iter()
218 .any(|activated| compatible(&candidate.package.version, activated.version()));
219
220 if has_conflicting {
221 conflicting.push(candidate.package_id());
227 continue;
228 }
229
230 let candidate_id = PackageId::new(
231 candidate.package.name.clone(),
232 candidate.package.version.clone(),
233 );
234
235 resolve.activate(
236 dependency_request.request_source.clone(),
237 dependency_request.package_alias.to_owned(),
238 dependency_request.origin_realm,
239 candidate_id.clone(),
240 );
241
242 resolve.metadata.insert(
243 candidate_id.clone(),
244 ResolvePackageMetadata {
245 realm: candidate.package.realm,
246 origin_realm: dependency_request.origin_realm,
247 source_registry: source_registry.clone(),
248 },
249 );
250
251 for (alias, req) in &candidate.dependencies {
252 packages_to_visit.push_back(DependencyRequest {
253 request_source: candidate_id.clone(),
254 request_realm: Realm::Shared,
255 origin_realm: dependency_request.origin_realm,
256 package_alias: alias.clone(),
257 package_req: req.clone(),
258 })
259 }
260
261 for (alias, req) in &candidate.server_dependencies {
262 packages_to_visit.push_back(DependencyRequest {
263 request_source: candidate_id.clone(),
264 request_realm: Realm::Server,
265 origin_realm: dependency_request.origin_realm,
266 package_alias: alias.clone(),
267 package_req: req.clone(),
268 })
269 }
270
271 continue 'outer;
272 }
273
274 if conflicting.is_empty() {
275 bail!(
276 "No packages were found that matched ({req_realm:?}) {req}.\
277 \nAre you sure this is a {req_realm:?} dependency?",
278 req_realm = dependency_request.request_realm,
279 req = dependency_request.package_req,
280 );
281 } else {
282 let conflicting_debug: Vec<_> = conflicting
283 .into_iter()
284 .map(|id| format!("{:?}", id))
285 .collect();
286
287 bail!(
288 "All possible candidates for package {req} ({req_realm:?}) \
289 conflicted with other packages that were already installed. \
290 These packages were previously selected: {conflicting}",
291 req = dependency_request.package_req,
292 req_realm = dependency_request.request_realm,
293 conflicting = conflicting_debug.join(", "),
294 );
295 }
296 }
297
298 Ok(resolve)
299}
300
301fn compatible(a: &Version, b: &Version) -> bool {
302 if a == b {
303 return true;
304 }
305
306 if a.major == 0 && b.major == 0 {
307 a.minor == b.minor
308 } else {
309 a.major == b.major
310 }
311}
312
313pub struct DependencyRequest {
314 request_source: PackageId,
315 request_realm: Realm,
316 origin_realm: Realm,
317 package_alias: String,
318 package_req: PackageReq,
319}
320
321#[cfg(test)]
322mod tests {
323 use super::*;
324
325 use crate::{
326 package_name::PackageName, package_source::InMemoryRegistry, test_package::PackageBuilder,
327 };
328
329 fn test_project(registry: InMemoryRegistry, package: PackageBuilder) -> anyhow::Result<()> {
330 let package_sources = PackageSourceMap::new(Box::new(registry.source()));
331 let manifest = package.into_manifest();
332 let resolve = resolve(&manifest, &Default::default(), &package_sources)?;
333 insta::assert_yaml_snapshot!(resolve);
334 Ok(())
335 }
336
337 #[test]
338 fn minimal() -> anyhow::Result<()> {
339 let registry = InMemoryRegistry::new();
340
341 let root = PackageBuilder::new("biff/minimal@0.1.0");
342 test_project(registry, root)
343 }
344
345 #[test]
346 fn one_dependency() -> anyhow::Result<()> {
347 let registry = InMemoryRegistry::new();
348 registry.publish(PackageBuilder::new("biff/minimal@0.1.0"));
349 registry.publish(PackageBuilder::new("biff/minimal@0.2.0"));
350
351 let root = PackageBuilder::new("biff/one-dependency@0.1.0")
352 .with_dep("Minimal", "biff/minimal@0.1.0");
353 test_project(registry, root)
354 }
355
356 #[test]
357 fn transitive_dependency() -> anyhow::Result<()> {
358 let registry = InMemoryRegistry::new();
359 registry.publish(PackageBuilder::new("biff/minimal@0.1.0"));
360 registry.publish(
361 PackageBuilder::new("biff/one-dependency@0.1.0")
362 .with_dep("Minimal", "biff/minimal@0.1.0"),
363 );
364
365 let root = PackageBuilder::new("biff/transitive-dependency@0.1.0")
366 .with_dep("OneDependency", "biff/one-dependency@0.1.0");
367 test_project(registry, root)
368 }
369
370 #[test]
373 fn unified_dependencies() -> anyhow::Result<()> {
374 let registry = InMemoryRegistry::new();
375 registry.publish(PackageBuilder::new("biff/b@1.0.0").with_dep("D", "biff/d@1.0.0"));
376 registry.publish(PackageBuilder::new("biff/c@1.0.0").with_dep("D", "biff/d@1.0.0"));
377 registry.publish(PackageBuilder::new("biff/d@1.0.0"));
378
379 let root = PackageBuilder::new("biff/a@1.0.0")
380 .with_dep("B", "biff/b@1.0.0")
381 .with_dep("C", "biff/c@1.0.0");
382
383 test_project(registry, root)
384 }
385
386 #[test]
390 fn server_to_shared() -> anyhow::Result<()> {
391 let registry = InMemoryRegistry::new();
392 registry.publish(PackageBuilder::new("biff/shared@1.0.0"));
393 registry.publish(
394 PackageBuilder::new("biff/server@1.0.0")
395 .with_realm(Realm::Server)
396 .with_dep("Shared", "biff/shared@1.0.0"),
397 );
398
399 let root =
400 PackageBuilder::new("biff/root@1.0.0").with_server_dep("Server", "biff/server@1.0.0");
401
402 test_project(registry, root)
403 }
404
405 #[test]
408 fn server_to_shared_and_shared_to_shared() -> anyhow::Result<()> {
409 let registry = InMemoryRegistry::new();
410 registry.publish(PackageBuilder::new("biff/shared@1.0.0"));
411 registry.publish(
412 PackageBuilder::new("biff/server@1.0.0")
413 .with_realm(Realm::Server)
414 .with_dep("Shared", "biff/shared@1.0.0"),
415 );
416
417 let root = PackageBuilder::new("biff/root@1.0.0")
418 .with_server_dep("Server", "biff/server@1.0.0")
419 .with_dep("Shared", "biff/shared@1.0.0");
420
421 test_project(registry, root)
422 }
423
424 #[test]
427 fn shared_to_server() -> anyhow::Result<()> {
428 let registry = InMemoryRegistry::new();
429 registry.publish(PackageBuilder::new("biff/server@1.0.0").with_realm(Realm::Server));
430
431 let root =
432 PackageBuilder::new("biff/root@1.0.0").with_server_dep("Server", "biff/server@1.0.0");
433
434 test_project(registry, root)
435 }
436
437 #[test]
438 fn fail_server_in_shared() {
439 let registry = InMemoryRegistry::new();
440 registry.publish(PackageBuilder::new("biff/server@1.0.0").with_realm(Realm::Server));
441
442 let root = PackageBuilder::new("biff/root@1.0.0").with_dep("Server", "biff/server@1.0.0");
443
444 let package_sources = PackageSourceMap::new(Box::new(registry.source()));
445 let err = resolve(root.manifest(), &Default::default(), &package_sources).unwrap_err();
446 insta::assert_display_snapshot!(err);
447 }
448
449 #[test]
454 fn one_dependency_no_upgrade() -> anyhow::Result<()> {
455 let registry = InMemoryRegistry::new();
456 registry.publish(PackageBuilder::new("biff/minimal@1.0.0"));
457
458 let root = PackageBuilder::new("biff/one-dependency@1.0.0")
459 .with_dep("Minimal", "biff/minimal@1.0.0");
460
461 let package_sources = PackageSourceMap::new(Box::new(registry.source()));
462
463 let resolved = resolve(root.manifest(), &Default::default(), &package_sources)?;
464 insta::assert_yaml_snapshot!("one_dependency_no_upgrade", resolved);
465
466 registry.publish(PackageBuilder::new("biff/minimal@1.1.0"));
467 let new_resolved = resolve(root.manifest(), &resolved.activated, &package_sources)?;
468 insta::assert_yaml_snapshot!("one_dependency_no_upgrade", new_resolved);
469
470 Ok(())
471 }
472
473 #[test]
474 fn one_dependency_yes_upgrade() -> anyhow::Result<()> {
475 let registry = InMemoryRegistry::new();
476 registry.publish(PackageBuilder::new("biff/minimal@1.0.0"));
477
478 let root = PackageBuilder::new("biff/one-dependency@1.0.0")
479 .with_dep("Minimal", "biff/minimal@1.0.0");
480
481 let package_sources = PackageSourceMap::new(Box::new(registry.source()));
482
483 let resolved = resolve(root.manifest(), &Default::default(), &package_sources)?;
484 insta::assert_yaml_snapshot!(resolved);
485
486 let remove_this: PackageName = "biff/minimal".parse().unwrap();
489 let try_to_use = resolved
490 .activated
491 .into_iter()
492 .filter(|id| id.name() != &remove_this)
493 .collect();
494
495 registry.publish(PackageBuilder::new("biff/minimal@1.1.0"));
496 let new_resolved = resolve(root.manifest(), &try_to_use, &package_sources)?;
497 insta::assert_yaml_snapshot!(new_resolved);
498
499 Ok(())
500 }
501}