1use std::{collections::HashMap, path::PathBuf};
2
3use nickel_lang_core::{cache::normalize_path, identifier::Ident, package::PackageMap};
4use pubgrub::{DefaultStringReporter, DependencyProvider, Reporter as _};
5
6use crate::{
7 Dependency, IndexDependency, ManifestFile, PreciseGitPkg, PreciseIndexPkg, PrecisePkg,
8 UnversionedPrecisePkg,
9 config::Config,
10 error::{Error, IoResultExt as _},
11 index::{self, PackageIndex, Shared},
12 lock::{LockFile, LockPrecisePkg},
13 snapshot::Snapshot,
14 version::{SemVer, VersionReq},
15};
16
17pub type ResolveError = pubgrub::PubGrubError<PackageRegistry>;
18
19pub fn print_resolve_error(f: &mut std::fmt::Formatter<'_>, e: &ResolveError) -> std::fmt::Result {
20 match e {
21 pubgrub::PubGrubError::NoSolution(derivation_tree) => {
22 let mut tree = derivation_tree.clone();
23 tree.collapse_no_versions();
24 write!(f, "{}", DefaultStringReporter::report(&tree))
25 }
26 pubgrub::PubGrubError::ErrorRetrievingDependencies {
27 package: _,
28 version: _,
29 source,
30 } => write!(f, "{source}"),
31 pubgrub::PubGrubError::ErrorChoosingVersion { package: _, source } => write!(f, "{source}"),
32 pubgrub::PubGrubError::ErrorInShouldCancel(_) => unreachable!(),
34 }
35}
36
37pub struct PackageRegistry {
38 previously_locked: HashMap<Package, SemVer>,
43 index: PackageIndex<Shared>,
44 snapshot: Snapshot,
45}
46
47#[derive(Debug, Clone, Eq, PartialEq, Hash)]
48pub enum Package {
49 Root,
50 Git(PreciseGitPkg),
51 Path(PathBuf),
52 Index(Bucket),
53}
54
55impl Package {
56 fn unversioned_or_index(self) -> Result<UnversionedPrecisePkg, Bucket> {
57 match self {
58 Package::Root => Ok(UnversionedPrecisePkg::Path(PathBuf::new())),
59 Package::Path(p) => Ok(UnversionedPrecisePkg::Path(p)),
60 Package::Git(g) => Ok(UnversionedPrecisePkg::Git(g)),
61 Package::Index(bucket) => Err(bucket),
62 }
63 }
64}
65
66#[derive(Debug, Clone, Eq, PartialEq, Hash)]
67pub struct Bucket {
68 pub id: index::Id,
69 pub version: BucketVersion,
70}
71
72#[derive(Debug, Clone, Eq, PartialEq, Hash)]
74pub enum BucketVersion {
75 Major(u64),
78 Minor(u64),
81 Prerelease(SemVer),
85}
86
87impl From<VersionReq> for BucketVersion {
88 fn from(req: VersionReq) -> Self {
89 match req {
90 VersionReq::Compatible(prefix) => {
91 BucketVersion::major_minor(prefix.major, prefix.minor.unwrap_or_default())
92 }
93 VersionReq::Exact(v) => v.into(),
94 }
95 }
96}
97
98impl From<SemVer> for BucketVersion {
99 fn from(v: SemVer) -> Self {
100 if v.pre.is_empty() {
101 BucketVersion::major_minor(v.major, v.minor)
102 } else {
103 BucketVersion::Prerelease(v.clone())
104 }
105 }
106}
107
108impl BucketVersion {
109 pub fn major_minor(major: u64, minor: u64) -> Self {
110 if major == 0 {
111 BucketVersion::Minor(minor)
112 } else {
113 BucketVersion::Major(major)
114 }
115 }
116
117 pub fn contains(&self, semver: &SemVer) -> bool {
118 match self {
119 BucketVersion::Major(v) => *v == semver.major && semver.pre.is_empty(),
120 BucketVersion::Minor(v) => {
121 semver.major == 0 && semver.minor == *v && semver.pre.is_empty()
122 }
123 BucketVersion::Prerelease(v) => v == semver,
124 }
125 }
126}
127
128impl std::fmt::Display for BucketVersion {
129 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
130 match self {
131 BucketVersion::Major(v) => write!(f, "{v}"),
132 BucketVersion::Minor(v) => write!(f, "0.{v}"),
133 BucketVersion::Prerelease(v) => write!(f, "{v}"),
134 }
135 }
136}
137
138impl std::fmt::Display for Package {
139 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
140 match self {
141 Package::Root => {
142 write!(f, "top-level package")
143 }
144 Package::Git(PreciseGitPkg { url, id, path }) => {
145 write!(f, "{url}@{id}/{}", path.display())
146 }
147 Package::Path(path) => {
148 write!(f, "'Path {}", path.display())
149 }
150 Package::Index(b) => {
151 write!(f, "{}", b.id)
152 }
153 }
154 }
155}
156
157#[derive(Copy, Clone, Debug, PartialOrd, Ord, PartialEq, Eq)]
163pub struct Priority {
164 pub single_version: bool,
165 pub conflicts: u32,
166}
167
168fn collect_intersections(
170 deps: impl Iterator<Item = (Package, pubgrub::Ranges<SemVer>)>,
171) -> pubgrub::Map<Package, pubgrub::Ranges<SemVer>> {
172 let mut ret = pubgrub::Map::default();
173 for (pkg, ranges) in deps {
174 ret.entry(pkg)
175 .and_modify(|e: &mut pubgrub::Ranges<_>| *e = e.intersection(&ranges))
176 .or_insert(ranges);
177 }
178
179 ret
185}
186
187impl PackageRegistry {
188 fn snapshot_dependencies(
196 &self,
197 pkg: &UnversionedPrecisePkg,
198 ) -> pubgrub::Map<Package, pubgrub::Ranges<SemVer>> {
199 let index_deps = self
200 .snapshot
201 .index_deps(pkg)
202 .map(index_dep_package_and_range);
203 let other_deps = self
204 .snapshot
205 .sorted_unversioned_dependencies(pkg)
206 .into_iter()
207 .map(|(_name, _dep, pkg)| {
208 let p = match pkg {
209 UnversionedPrecisePkg::Git(g) => Package::Git(g),
210 UnversionedPrecisePkg::Path(path) => Package::Path(path),
211 };
212 (p, pubgrub::Ranges::full())
213 });
214
215 collect_intersections(index_deps.chain(other_deps))
216 }
217}
218
219impl DependencyProvider for PackageRegistry {
220 type P = Package;
221 type V = SemVer;
222 type VS = pubgrub::Ranges<SemVer>;
223 type Priority = Priority;
224 type M = String;
225 type Err = crate::Error;
226
227 fn prioritize(
228 &self,
229 package: &Self::P,
230 _range: &Self::VS,
231 package_conflicts_counts: &pubgrub::PackageResolutionStatistics,
232 ) -> Self::Priority {
233 let single_version = match package {
234 Package::Git { .. } | Package::Path { .. } | Package::Root => true,
235 Package::Index(Bucket {
236 version: BucketVersion::Prerelease(_),
237 ..
238 }) => true,
239 Package::Index(_) => false,
242 };
243 Priority {
244 single_version,
245 conflicts: package_conflicts_counts.conflict_count(),
246 }
247 }
248
249 fn choose_version(
250 &self,
251 package: &Self::P,
252 range: &Self::VS,
253 ) -> Result<Option<Self::V>, Self::Err> {
254 let check_version = |v| {
255 if range.contains(v) {
256 Ok(Some(v.clone()))
257 } else {
258 Ok(None)
259 }
260 };
261
262 if let Some(locked_version) = self.previously_locked.get(package)
263 && range.contains(locked_version)
264 {
265 return Ok(Some(locked_version.clone()));
266 }
267
268 match package {
269 Package::Git(g) => check_version(
270 &self
271 .snapshot
272 .manifest(&UnversionedPrecisePkg::Git(g.clone()))
273 .version,
274 ),
275 Package::Path(path) => check_version(
276 &self
278 .snapshot
279 .manifest(&UnversionedPrecisePkg::Path(path.clone()))
280 .version,
281 ),
282 Package::Index(bucket) => {
283 if let BucketVersion::Prerelease(v) = &bucket.version {
284 if self.index.has_version(&bucket.id, v)? {
285 Ok(Some(v.clone()))
286 } else {
287 Ok(None)
288 }
289 } else {
290 let min_version = self
293 .index
294 .available_versions(&bucket.id)?
295 .find(|v| bucket.version.contains(v) && range.contains(v));
296 Ok(min_version)
297 }
298 }
299 Package::Root => check_version(
300 &self
301 .snapshot
302 .manifest(&UnversionedPrecisePkg::Path(PathBuf::new()))
303 .version,
304 ),
305 }
306 }
307
308 fn get_dependencies(
309 &self,
310 package: &Self::P,
311 version: &Self::V,
312 ) -> Result<pubgrub::Dependencies<Self::P, Self::VS, Self::M>, Self::Err> {
313 let deps: pubgrub::Map<_, _> = match package.clone().unversioned_or_index() {
314 Ok(uv) => self.snapshot_dependencies(&uv),
315 Err(bucket) => {
316 let index_package = self.index.package(&bucket.id, version)?;
317 collect_intersections(
318 index_package
319 .dependencies
320 .values()
321 .map(index_dep_package_and_range),
322 )
323 }
324 };
325
326 Ok(pubgrub::Dependencies::Available(deps))
327 }
328}
329
330fn index_dep_package_and_range(dep: &IndexDependency) -> (Package, pubgrub::Ranges<SemVer>) {
331 let p = Package::Index(Bucket {
332 id: dep.id.clone(),
333 version: dep.version.clone().into(),
334 });
335 let range = match &dep.version {
336 VersionReq::Compatible(v) => {
337 let lower_bound = SemVer::new(
338 v.major,
339 v.minor.unwrap_or_default(),
340 v.patch.unwrap_or_default(),
341 );
342 pubgrub::Ranges::higher_than(lower_bound)
343 }
344 VersionReq::Exact(req) => pubgrub::Ranges::singleton(req.clone()),
345 };
346
347 (p, range)
348}
349
350#[derive(Debug)]
352pub struct Resolution {
353 pub config: Config,
354 pub snapshot: Snapshot,
359 pub index: PackageIndex<Shared>,
360 pub index_packages: HashMap<index::Id, Vec<SemVer>>,
365}
366
367pub fn resolve(
369 manifest: &ManifestFile,
370 snapshot: Snapshot,
371 index: PackageIndex<Shared>,
372 config: Config,
373) -> Result<Resolution, Error> {
374 resolve_with_lock(manifest, &LockFile::default(), snapshot, index, config)
375}
376
377pub fn resolve_with_lock(
384 manifest: &ManifestFile,
385 lock: &LockFile,
386 snapshot: Snapshot,
387 index: PackageIndex<Shared>,
388 config: Config,
389) -> Result<Resolution, Error> {
390 let version = manifest.version.clone();
391 let registry = PackageRegistry {
392 previously_locked: lock
393 .packages
394 .values()
395 .filter_map(|entry| {
396 let LockPrecisePkg::Index { id, version } = &entry.precise else {
397 return None;
398 };
399
400 let pkg = Package::Index(Bucket {
401 id: id.clone(),
402 version: version.clone().into(),
403 });
404
405 Some((pkg, version.clone()))
406 })
407 .collect(),
408 index,
409 snapshot,
410 };
411
412 let deps = pubgrub::resolve(®istry, Package::Root, version)
413 .map_err(|e| Error::Resolution(Box::new(e)))?;
414
415 let mut index_packages: HashMap<index::Id, Vec<SemVer>> = HashMap::new();
416 for (pkg, version) in deps {
417 if let Package::Index(bucket) = pkg {
418 index_packages.entry(bucket.id).or_default().push(version);
419 }
420 }
421 for list in index_packages.values_mut() {
422 list.sort();
423 list.dedup();
424 }
425
426 Ok(Resolution {
427 config,
428 snapshot: registry.snapshot,
429 index: registry.index,
430 index_packages,
431 })
432}
433
434pub fn copy_from_lock(
442 lock: &LockFile,
443 snapshot: Snapshot,
444 index: PackageIndex<Shared>,
445 config: Config,
446) -> Result<Resolution, Error> {
447 let mut index_packages: HashMap<index::Id, Vec<SemVer>> = HashMap::new();
448
449 for entry in lock.packages.values() {
450 if let LockPrecisePkg::Index { id, version } = &entry.precise {
451 index_packages
452 .entry(id.clone())
453 .or_default()
454 .push(version.clone());
455 }
456 }
457 for list in index_packages.values_mut() {
458 list.sort();
459 list.dedup();
460 }
461
462 Ok(Resolution {
463 config,
464 snapshot,
465 index,
466 index_packages,
467 })
468}
469
470impl Resolution {
471 fn index_dep_version(&self, dep: &IndexDependency) -> &SemVer {
478 self.index_packages
480 .get(&dep.id)
481 .unwrap()
482 .iter()
483 .find(|v| dep.version.matches(v))
487 .unwrap()
488 }
489
490 pub fn precise(&self, dep: &Dependency) -> PrecisePkg {
497 match dep {
498 Dependency::Git(git) => PrecisePkg::Git(PreciseGitPkg {
499 url: git.url.clone(),
500 id: self.snapshot.git[git],
501 path: git.path.clone(),
502 }),
503 Dependency::Path(path) => PrecisePkg::Path(path.to_owned()),
504 Dependency::Index(idx) => {
505 let version = self.index_dep_version(idx).clone();
506 PrecisePkg::Index(PreciseIndexPkg {
507 id: idx.id.clone(),
508 version,
509 })
510 }
511 }
512 }
513
514 pub fn sorted_dependencies(
521 &self,
522 pkg: &PrecisePkg,
523 ) -> Result<Vec<(Ident, Dependency, PrecisePkg)>, Error> {
524 match pkg.clone().unversioned_or_index() {
525 Ok(uv) => {
526 let mut deps: Vec<_> = self
527 .snapshot
528 .sorted_unversioned_dependencies(&uv)
529 .into_iter()
530 .map(|(i, d, p)| (i, d, p.into()))
531 .collect();
532 let index_deps =
533 self.snapshot
534 .manifest(&uv)
535 .dependencies
536 .iter()
537 .filter_map(|(id, dep)| {
538 if matches!(dep, Dependency::Index(_)) {
539 Some((*id, dep.clone(), self.precise(dep)))
540 } else {
541 None
542 }
543 });
544 deps.extend(index_deps);
545 deps.sort_by(|(name0, _, _), (name1, _, _)| name0.label().cmp(name1.label()));
546 deps.dedup();
547 Ok(deps)
548 }
549 Err(PreciseIndexPkg { id, version }) => {
550 let pkg = self.index.package(&id, &version)?;
551 let mut ret: Vec<_> = pkg
552 .dependencies
553 .iter()
554 .map(|(id, index_dep)| {
555 let semver = self.index_packages[&index_dep.id]
556 .iter()
557 .find(|v| index_dep.version.matches(v))
558 .unwrap();
559 (
560 *id,
561 Dependency::Index(index_dep.clone()),
562 PrecisePkg::Index(PreciseIndexPkg {
563 id: index_dep.id.clone(),
564 version: semver.clone(),
565 }),
566 )
567 })
568 .collect();
569 ret.sort_by(|(name0, _, _), (name1, _, _)| name0.label().cmp(name1.label()));
570 Ok(ret)
571 }
572 }
573 }
574
575 pub fn package_map(&self, manifest: &ManifestFile) -> Result<PackageMap, Error> {
577 let parent_dir = manifest.parent_dir.clone();
578 let manifest_dir = normalize_path(&parent_dir).with_path(&parent_dir)?;
579 let config = &self.config;
580
581 let mut all: Vec<PrecisePkg> = self
582 .snapshot
583 .all_packages()
584 .map(|p| p.clone().into())
585 .collect();
586 all.extend(self.index_packages.iter().flat_map(|(id, versions)| {
587 versions.iter().map(|v| {
588 PrecisePkg::Index(PreciseIndexPkg {
589 id: id.clone(),
590 version: v.clone(),
591 })
592 })
593 }));
594 all.sort();
595 all.dedup();
596
597 let mut packages = HashMap::new();
598 for p in &all {
599 let p_path = p
600 .clone()
601 .with_abs_path(&manifest_dir)
602 .local_path(config, &self.index)?;
603 let root_path = &manifest_dir;
604 for (dep_id, _, dep_precise) in self.sorted_dependencies(p)? {
605 packages.insert(
606 (p_path.clone(), dep_id),
607 dep_precise
608 .with_abs_path(root_path)
609 .local_path(config, &self.index)?,
610 );
611 }
612 }
613
614 Ok(PackageMap {
615 top_level: manifest
617 .dependencies
618 .iter()
619 .map(|(name, source)| {
620 Ok((
621 *name,
622 self.precise(source)
623 .with_abs_path(&manifest_dir)
624 .local_path(config, &self.index)?,
625 ))
626 })
627 .collect::<Result<_, Error>>()?,
628
629 packages,
630 })
631 }
632
633 pub fn dependencies(&self, pkg: &PrecisePkg) -> Result<HashMap<Ident, PrecisePkg>, Error> {
635 let ret = match pkg.clone().unversioned_or_index() {
636 Ok(uv) => {
637 let manifest = self.snapshot.manifest(&uv);
638 manifest
639 .dependencies
640 .iter()
641 .map(move |(dep_name, dep)| {
642 let pkg = match dep.clone().as_unversioned() {
643 Some(dep) => self.snapshot.dependency(&uv, &dep).clone().into(),
644 None => {
645 self.precise(dep)
648 }
649 };
650 (*dep_name, pkg)
651 })
652 .collect()
653 }
654 Err(PreciseIndexPkg { id, version }) => {
655 let index_pkg = self.index.package(&id, &version)?;
656 index_pkg
657 .dependencies
658 .into_iter()
659 .map(move |(dep_name, dep)| {
660 let precise_dep = self.precise(&Dependency::Index(IndexDependency {
661 id: dep.id.clone(),
662 version: dep.version.clone(),
663 }));
664 (dep_name, precise_dep)
665 })
666 .collect()
667 }
668 };
669 Ok(ret)
670 }
671
672 pub fn all_packages(&self) -> Vec<PrecisePkg> {
674 let mut ret: Vec<_> = self
675 .snapshot
676 .all_packages()
677 .map(|p| p.clone().into())
678 .collect();
679 ret.extend(self.index_packages.iter().flat_map(|(id, vs)| {
680 vs.iter().map(|v| {
681 PrecisePkg::Index(PreciseIndexPkg {
682 id: id.clone(),
683 version: v.clone(),
684 })
685 })
686 }));
687 ret.sort();
688 ret.dedup();
689 ret
690 }
691}