1use std::collections::{BTreeMap, BTreeSet};
7use std::convert::Infallible;
8
9use pubgrub::{
10 Dependencies, DependencyConstraints, DependencyProvider, PackageResolutionStatistics, Ranges,
11};
12use semver::Version;
13
14use crate::error::PkgError;
15use crate::version::{parse_version, parse_version_req, req_to_pubgrub_range};
16
17pub type SemverRanges = Ranges<Version>;
19
20pub type ResolvedDeps = BTreeMap<String, Version>;
22
23pub type UnifiedFeatures = BTreeMap<String, BTreeSet<String>>;
25
26#[derive(Debug, Clone, Copy, PartialEq, Eq)]
28pub enum DepVisibility {
29 Public,
31 Private,
33}
34
35#[derive(Debug, Clone, Default)]
37pub struct PackageVersionMeta {
38 pub deps: BTreeMap<String, String>,
40 pub dep_features: BTreeMap<String, Vec<String>>,
42 pub supported_targets: Option<Vec<String>>,
44 pub available_features: BTreeMap<String, Vec<String>>,
46}
47
48#[derive(Debug, Clone, Default)]
53pub struct PackageRegistry {
54 packages: BTreeMap<String, BTreeMap<Version, PackageVersionMeta>>,
56}
57
58impl PackageRegistry {
59 #[must_use]
61 pub fn new() -> Self {
62 Self::default()
63 }
64
65 pub fn register(
69 &mut self,
70 name: &str,
71 version: &str,
72 deps: BTreeMap<String, String>,
73 ) -> Result<(), PkgError> {
74 let meta = PackageVersionMeta {
75 deps,
76 ..Default::default()
77 };
78 self.register_with_meta(name, version, meta)
79 }
80
81 pub fn register_with_meta(
83 &mut self,
84 name: &str,
85 version: &str,
86 meta: PackageVersionMeta,
87 ) -> Result<(), PkgError> {
88 let ver = parse_version(version)?;
89 self.packages
90 .entry(name.to_string())
91 .or_default()
92 .insert(ver, meta);
93 Ok(())
94 }
95
96 #[must_use]
98 pub fn available_versions(&self, name: &str) -> Vec<&Version> {
99 self.packages
100 .get(name)
101 .map(|versions| versions.keys().collect())
102 .unwrap_or_default()
103 }
104
105 #[must_use]
107 pub fn has_package(&self, name: &str) -> bool {
108 self.packages.contains_key(name)
109 }
110
111 pub fn resolve(
115 &self,
116 root_name: &str,
117 root_version: &str,
118 direct_deps: &BTreeMap<String, String>,
119 ) -> Result<ResolvedDeps, PkgError> {
120 self.resolve_internal(root_name, root_version, direct_deps, None)
121 }
122
123 pub fn resolve_for_target(
128 &self,
129 root_name: &str,
130 root_version: &str,
131 direct_deps: &BTreeMap<String, String>,
132 target: &str,
133 ) -> Result<ResolvedDeps, PkgError> {
134 self.resolve_internal(
135 root_name,
136 root_version,
137 direct_deps,
138 Some(target.to_string()),
139 )
140 }
141
142 fn resolve_internal(
143 &self,
144 root_name: &str,
145 root_version: &str,
146 direct_deps: &BTreeMap<String, String>,
147 active_target: Option<String>,
148 ) -> Result<ResolvedDeps, PkgError> {
149 let root_ver = parse_version(root_version)?;
150
151 let provider = ResolverProvider {
153 registry: self,
154 root_package: root_name.to_string(),
155 root_version: root_ver.clone(),
156 root_deps: direct_deps.clone(),
157 active_target,
158 };
159
160 let result = pubgrub::resolve(&provider, root_name.to_string(), root_ver).map_err(|e| {
161 let msg = format!("{e}");
162 if msg.contains("No solution") || msg.contains("conflict") {
164 PkgError::UnresolvableConstraints(vec![msg])
165 } else {
166 PkgError::ResolutionFailed(msg)
167 }
168 })?;
169
170 let mut resolved = BTreeMap::new();
172 for (pkg, ver) in result {
173 if pkg != root_name {
174 resolved.insert(pkg, ver);
175 }
176 }
177
178 Ok(resolved)
179 }
180
181 #[must_use]
186 pub fn unify_features(
187 &self,
188 root_dep_features: &BTreeMap<String, Vec<String>>,
189 resolved: &ResolvedDeps,
190 ) -> UnifiedFeatures {
191 let mut unified: BTreeMap<String, BTreeSet<String>> = BTreeMap::new();
192
193 for (name, feats) in root_dep_features {
195 if resolved.contains_key(name) && !feats.is_empty() {
196 unified
197 .entry(name.clone())
198 .or_default()
199 .extend(feats.iter().cloned());
200 }
201 }
202
203 for (pkg_name, ver) in resolved {
205 if let Some(versions) = self.packages.get(pkg_name) {
206 if let Some(meta) = versions.get(ver) {
207 for (dep_name, feats) in &meta.dep_features {
208 if resolved.contains_key(dep_name) && !feats.is_empty() {
209 unified
210 .entry(dep_name.clone())
211 .or_default()
212 .extend(feats.iter().cloned());
213 }
214 }
215 }
216 }
217 }
218
219 unified
220 }
221}
222
223#[must_use]
229pub fn compute_visibility(
230 direct_dep_names: &BTreeMap<String, String>,
231 resolved: &ResolvedDeps,
232) -> BTreeMap<String, DepVisibility> {
233 resolved
234 .keys()
235 .map(|name| {
236 let vis = if direct_dep_names.contains_key(name) {
237 DepVisibility::Public
238 } else {
239 DepVisibility::Private
240 };
241 (name.clone(), vis)
242 })
243 .collect()
244}
245
246struct ResolverProvider<'a> {
248 registry: &'a PackageRegistry,
249 root_package: String,
250 root_version: Version,
251 root_deps: BTreeMap<String, String>,
252 active_target: Option<String>,
253}
254
255impl<'a> DependencyProvider for ResolverProvider<'a> {
256 type P = String;
257 type V = Version;
258 type VS = Ranges<Version>;
259 type M = String;
260 type Err = Infallible;
261 type Priority = u32;
262
263 fn prioritize(
264 &self,
265 package: &String,
266 _range: &Ranges<Version>,
267 _stats: &PackageResolutionStatistics,
268 ) -> Self::Priority {
269 if package == &self.root_package {
271 return u32::MAX;
272 }
273 let count = self
274 .registry
275 .packages
276 .get(package)
277 .map(|v| v.len())
278 .unwrap_or(0);
279 u32::MAX.saturating_sub(count as u32)
281 }
282
283 fn choose_version(
284 &self,
285 package: &String,
286 range: &Ranges<Version>,
287 ) -> Result<Option<Version>, Infallible> {
288 if package == &self.root_package {
289 if range.contains(&self.root_version) {
290 return Ok(Some(self.root_version.clone()));
291 }
292 return Ok(None);
293 }
294
295 let version = self.registry.packages.get(package).and_then(|versions| {
297 versions
298 .keys()
299 .rev()
300 .find(|v| {
301 if !range.contains(v) {
302 return false;
303 }
304 if let Some(target) = &self.active_target {
306 if let Some(meta) = versions.get(v) {
307 if let Some(supported) = &meta.supported_targets {
308 return supported.iter().any(|t| t == target);
309 }
310 }
311 }
312 true
313 })
314 .cloned()
315 });
316
317 Ok(version)
318 }
319
320 fn get_dependencies(
321 &self,
322 package: &String,
323 version: &Version,
324 ) -> Result<Dependencies<String, Ranges<Version>, String>, Infallible> {
325 if package == &self.root_package && version == &self.root_version {
326 let deps: DependencyConstraints<String, Ranges<Version>> = self
328 .root_deps
329 .iter()
330 .filter_map(|(name, req_str)| {
331 parse_version_req(req_str)
332 .ok()
333 .map(|req| (name.clone(), req_to_pubgrub_range(&req)))
334 })
335 .collect();
336 return Ok(Dependencies::Available(deps));
337 }
338
339 let Some(versions) = self.registry.packages.get(package) else {
340 return Ok(Dependencies::Unavailable(format!(
341 "package '{package}' not found in registry"
342 )));
343 };
344
345 let Some(meta) = versions.get(version) else {
346 return Ok(Dependencies::Unavailable(format!(
347 "version {version} of '{package}' not found"
348 )));
349 };
350
351 let deps: DependencyConstraints<String, Ranges<Version>> = meta
352 .deps
353 .iter()
354 .filter_map(|(name, req_str)| {
355 parse_version_req(req_str)
356 .ok()
357 .map(|req| (name.clone(), req_to_pubgrub_range(&req)))
358 })
359 .collect();
360
361 Ok(Dependencies::Available(deps))
362 }
363}
364
365#[cfg(test)]
366mod tests {
367 use super::*;
368
369 fn make_deps(pairs: &[(&str, &str)]) -> BTreeMap<String, String> {
370 pairs
371 .iter()
372 .map(|(k, v)| (k.to_string(), v.to_string()))
373 .collect()
374 }
375
376 #[test]
377 fn resolve_simple_deps() {
378 let mut registry = PackageRegistry::new();
379 registry.register("foo", "1.0.0", BTreeMap::new()).unwrap();
380 registry.register("foo", "1.1.0", BTreeMap::new()).unwrap();
381 registry
382 .register("bar", "2.0.0", make_deps(&[("foo", "^1.0")]))
383 .unwrap();
384
385 let direct = make_deps(&[("bar", "^2.0")]);
386 let resolved = registry.resolve("my-app", "0.1.0", &direct).unwrap();
387
388 assert!(resolved.contains_key("bar"));
389 assert!(resolved.contains_key("foo"));
390 assert_eq!(resolved["bar"], Version::new(2, 0, 0));
391 assert_eq!(resolved["foo"], Version::new(1, 1, 0));
393 }
394
395 #[test]
396 fn resolve_transitive_deps() {
397 let mut registry = PackageRegistry::new();
398 registry.register("c", "1.0.0", BTreeMap::new()).unwrap();
399 registry
400 .register("b", "1.0.0", make_deps(&[("c", "^1.0")]))
401 .unwrap();
402 registry
403 .register("a", "1.0.0", make_deps(&[("b", "^1.0")]))
404 .unwrap();
405
406 let direct = make_deps(&[("a", "^1.0")]);
407 let resolved = registry.resolve("root", "0.1.0", &direct).unwrap();
408
409 assert_eq!(resolved.len(), 3);
410 assert!(resolved.contains_key("a"));
411 assert!(resolved.contains_key("b"));
412 assert!(resolved.contains_key("c"));
413 }
414
415 #[test]
416 fn resolve_conflicting_deps() {
417 let mut registry = PackageRegistry::new();
418 registry
420 .register("shared", "1.0.0", BTreeMap::new())
421 .unwrap();
422 registry
424 .register("a", "1.0.0", make_deps(&[("shared", "^1.0")]))
425 .unwrap();
426 registry
428 .register("b", "1.0.0", make_deps(&[("shared", "^2.0")]))
429 .unwrap();
430
431 let direct = make_deps(&[("a", "^1.0"), ("b", "^1.0")]);
432 let result = registry.resolve("root", "0.1.0", &direct);
433 assert!(result.is_err());
434 }
435
436 #[test]
437 fn resolve_empty_deps() {
438 let registry = PackageRegistry::new();
439 let direct = BTreeMap::new();
440 let resolved = registry.resolve("my-app", "1.0.0", &direct).unwrap();
441 assert!(resolved.is_empty());
442 }
443
444 #[test]
445 fn resolve_picks_highest_compatible() {
446 let mut registry = PackageRegistry::new();
447 registry.register("foo", "1.0.0", BTreeMap::new()).unwrap();
448 registry.register("foo", "1.2.0", BTreeMap::new()).unwrap();
449 registry.register("foo", "1.5.0", BTreeMap::new()).unwrap();
450 registry.register("foo", "2.0.0", BTreeMap::new()).unwrap();
451
452 let direct = make_deps(&[("foo", "^1.0")]);
453 let resolved = registry.resolve("root", "0.1.0", &direct).unwrap();
454 assert_eq!(resolved["foo"], Version::new(1, 5, 0));
455 }
456
457 #[test]
460 fn target_filtering_skips_incompatible_packages() {
461 let mut registry = PackageRegistry::new();
462
463 registry
465 .register_with_meta(
466 "foo",
467 "1.0.0",
468 PackageVersionMeta {
469 deps: BTreeMap::new(),
470 supported_targets: Some(vec!["js".into()]),
471 ..Default::default()
472 },
473 )
474 .unwrap();
475
476 registry
478 .register_with_meta(
479 "foo",
480 "1.1.0",
481 PackageVersionMeta {
482 deps: BTreeMap::new(),
483 supported_targets: Some(vec!["js".into(), "rust".into()]),
484 ..Default::default()
485 },
486 )
487 .unwrap();
488
489 let direct = make_deps(&[("foo", "^1.0")]);
490
491 let resolved = registry
493 .resolve_for_target("root", "0.1.0", &direct, "js")
494 .unwrap();
495 assert_eq!(resolved["foo"], Version::new(1, 1, 0));
496
497 let resolved = registry
499 .resolve_for_target("root", "0.1.0", &direct, "rust")
500 .unwrap();
501 assert_eq!(resolved["foo"], Version::new(1, 1, 0));
502
503 let result = registry.resolve_for_target("root", "0.1.0", &direct, "python");
505 assert!(result.is_err());
506 }
507
508 #[test]
509 fn target_filtering_no_targets_means_all() {
510 let mut registry = PackageRegistry::new();
511
512 registry.register("foo", "1.0.0", BTreeMap::new()).unwrap();
514
515 let direct = make_deps(&[("foo", "^1.0")]);
516
517 let resolved = registry
519 .resolve_for_target("root", "0.1.0", &direct, "rust")
520 .unwrap();
521 assert_eq!(resolved["foo"], Version::new(1, 0, 0));
522 }
523
524 #[test]
525 fn target_filtering_transitive() {
526 let mut registry = PackageRegistry::new();
527
528 registry
530 .register_with_meta(
531 "inner",
532 "1.0.0",
533 PackageVersionMeta {
534 deps: BTreeMap::new(),
535 supported_targets: Some(vec!["rust".into()]),
536 ..Default::default()
537 },
538 )
539 .unwrap();
540
541 registry
543 .register("outer", "1.0.0", make_deps(&[("inner", "^1.0")]))
544 .unwrap();
545
546 let direct = make_deps(&[("outer", "^1.0")]);
547
548 let result = registry.resolve_for_target("root", "0.1.0", &direct, "js");
550 assert!(result.is_err());
551
552 let resolved = registry
554 .resolve_for_target("root", "0.1.0", &direct, "rust")
555 .unwrap();
556 assert!(resolved.contains_key("outer"));
557 assert!(resolved.contains_key("inner"));
558 }
559
560 #[test]
563 fn feature_unification_unions_features() {
564 let mut registry = PackageRegistry::new();
565
566 registry
568 .register_with_meta(
569 "shared",
570 "1.0.0",
571 PackageVersionMeta {
572 deps: BTreeMap::new(),
573 available_features: BTreeMap::from([
574 ("json".into(), vec![]),
575 ("xml".into(), vec![]),
576 ("yaml".into(), vec![]),
577 ]),
578 ..Default::default()
579 },
580 )
581 .unwrap();
582
583 registry
585 .register_with_meta(
586 "a",
587 "1.0.0",
588 PackageVersionMeta {
589 deps: make_deps(&[("shared", "^1.0")]),
590 dep_features: BTreeMap::from([("shared".into(), vec!["json".into()])]),
591 ..Default::default()
592 },
593 )
594 .unwrap();
595
596 registry
598 .register_with_meta(
599 "b",
600 "1.0.0",
601 PackageVersionMeta {
602 deps: make_deps(&[("shared", "^1.0")]),
603 dep_features: BTreeMap::from([(
604 "shared".into(),
605 vec!["xml".into(), "yaml".into()],
606 )]),
607 ..Default::default()
608 },
609 )
610 .unwrap();
611
612 let direct = make_deps(&[("a", "^1.0"), ("b", "^1.0")]);
613 let resolved = registry.resolve("root", "0.1.0", &direct).unwrap();
614
615 let unified = registry.unify_features(&BTreeMap::new(), &resolved);
616
617 let shared_features = &unified["shared"];
618 assert!(shared_features.contains("json"));
619 assert!(shared_features.contains("xml"));
620 assert!(shared_features.contains("yaml"));
621 assert_eq!(shared_features.len(), 3);
622 }
623
624 #[test]
625 fn feature_unification_includes_root_features() {
626 let mut registry = PackageRegistry::new();
627 registry.register("foo", "1.0.0", BTreeMap::new()).unwrap();
628
629 let direct = make_deps(&[("foo", "^1.0")]);
630 let resolved = registry.resolve("root", "0.1.0", &direct).unwrap();
631
632 let root_features = BTreeMap::from([("foo".into(), vec!["extra".into(), "debug".into()])]);
633 let unified = registry.unify_features(&root_features, &resolved);
634
635 let foo_features = &unified["foo"];
636 assert!(foo_features.contains("extra"));
637 assert!(foo_features.contains("debug"));
638 }
639
640 #[test]
641 fn feature_unification_empty_when_no_features() {
642 let mut registry = PackageRegistry::new();
643 registry.register("foo", "1.0.0", BTreeMap::new()).unwrap();
644
645 let direct = make_deps(&[("foo", "^1.0")]);
646 let resolved = registry.resolve("root", "0.1.0", &direct).unwrap();
647
648 let unified = registry.unify_features(&BTreeMap::new(), &resolved);
649 assert!(unified.is_empty());
650 }
651
652 #[test]
655 fn transitive_deps_are_private() {
656 let mut registry = PackageRegistry::new();
657 registry.register("c", "1.0.0", BTreeMap::new()).unwrap();
658 registry
659 .register("b", "1.0.0", make_deps(&[("c", "^1.0")]))
660 .unwrap();
661 registry
662 .register("a", "1.0.0", make_deps(&[("b", "^1.0")]))
663 .unwrap();
664
665 let direct = make_deps(&[("a", "^1.0")]);
666 let resolved = registry.resolve("root", "0.1.0", &direct).unwrap();
667
668 let visibility = compute_visibility(&direct, &resolved);
669
670 assert_eq!(visibility["a"], DepVisibility::Public);
672 assert_eq!(visibility["b"], DepVisibility::Private);
674 assert_eq!(visibility["c"], DepVisibility::Private);
675 }
676
677 #[test]
678 fn direct_deps_are_public() {
679 let mut registry = PackageRegistry::new();
680 registry.register("foo", "1.0.0", BTreeMap::new()).unwrap();
681 registry.register("bar", "1.0.0", BTreeMap::new()).unwrap();
682
683 let direct = make_deps(&[("foo", "^1.0"), ("bar", "^1.0")]);
684 let resolved = registry.resolve("root", "0.1.0", &direct).unwrap();
685
686 let visibility = compute_visibility(&direct, &resolved);
687
688 assert_eq!(visibility["foo"], DepVisibility::Public);
689 assert_eq!(visibility["bar"], DepVisibility::Public);
690 }
691
692 #[test]
693 fn visibility_mixed_direct_and_transitive() {
694 let mut registry = PackageRegistry::new();
695 registry
697 .register("shared", "1.0.0", BTreeMap::new())
698 .unwrap();
699 registry
700 .register("lib", "1.0.0", make_deps(&[("shared", "^1.0")]))
701 .unwrap();
702
703 let direct = make_deps(&[("lib", "^1.0"), ("shared", "^1.0")]);
705 let resolved = registry.resolve("root", "0.1.0", &direct).unwrap();
706
707 let visibility = compute_visibility(&direct, &resolved);
708
709 assert_eq!(visibility["lib"], DepVisibility::Public);
711 assert_eq!(visibility["shared"], DepVisibility::Public);
712 }
713}