Skip to main content

zoi_install/
pubgrub.rs

1use colored::Colorize;
2use pubgrub::{Dependencies, DependencyProvider, Ranges};
3use rusqlite::params;
4use rustc_hash::FxHashMap;
5use semver::Version;
6use std::cell::RefCell;
7use std::collections::HashMap;
8use std::fmt::Display;
9use thiserror::Error;
10use zoi_core::types;
11use zoi_db as db;
12use zoi_resolver::resolve;
13
14fn parse_pkgs_v2_key(key: &str) -> (String, String) {
15    let key = key.trim_start_matches('#');
16    let key = key.trim_start_matches('@');
17    if let Some((repo, name)) = key.split_once('/') {
18        let name = name.split(':').next().unwrap_or(name);
19        (repo.to_string(), name.to_string())
20    } else {
21        (String::new(), key.to_string())
22    }
23}
24
25#[derive(Clone, Debug, Eq, PartialEq, Hash)]
26pub struct PkgName {
27    pub name: String,
28    pub sub_package: Option<String>,
29    pub repo: String,
30    pub registry: String,
31    pub explicit_source: Option<String>,
32}
33
34impl Display for PkgName {
35    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
36        if let Some(source) = &self.explicit_source {
37            return write!(f, "{}", source);
38        }
39        if let Some(sub) = &self.sub_package {
40            write!(f, "#{}@{}/{}:{}", self.registry, self.repo, self.name, sub)
41        } else {
42            write!(f, "#{}@{}/{}", self.registry, self.repo, self.name)
43        }
44    }
45}
46
47#[derive(Clone, Debug, Eq, PartialEq, Hash)]
48pub struct SemVersion {
49    pub epoch: u32,
50    pub v: Version,
51    pub original: String,
52}
53
54impl SemVersion {
55    pub fn new(epoch: u32, v: Version, original: String) -> Self {
56        Self { epoch, v, original }
57    }
58
59    pub fn parse(v: &str) -> Result<Self, anyhow::Error> {
60        let (epoch, version_str) = if let Some((e_str, v_str)) = v.split_once(':') {
61            if let Ok(e) = e_str.parse::<u32>() {
62                (e, v_str)
63            } else {
64                (0, v)
65            }
66        } else {
67            (0, v)
68        };
69
70        let clean = sanitize_version_string(version_str);
71        match Version::parse(&clean) {
72            Ok(parsed) => Ok(SemVersion {
73                epoch,
74                v: parsed,
75                original: v.to_string(),
76            }),
77            Err(_) => {
78                // Fallback for extremely weird versions: use 0.0.0+original
79                Ok(SemVersion {
80                    epoch,
81                    v: Version::new(0, 0, 0),
82                    original: v.to_string(),
83                })
84            }
85        }
86    }
87}
88
89impl Ord for SemVersion {
90    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
91        self.epoch
92            .cmp(&other.epoch)
93            .then_with(|| self.v.cmp(&other.v))
94    }
95}
96
97impl PartialOrd for SemVersion {
98    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
99        Some(self.cmp(other))
100    }
101}
102
103impl Display for SemVersion {
104    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
105        write!(f, "{}", self.original)
106    }
107}
108
109/// Sanitizes non-standard version strings into valid SemVer format.
110///
111/// Logic:
112/// - Padds missing parts (e.g. "2.41" -> "2.41.0").
113/// - Converts extra parts into build metadata (e.g. "7.1.4.arch1" -> "7.1.4+arch1").
114/// - Cleans up prefixes and suffixes.
115fn sanitize_version_string(v: &str) -> String {
116    let v = v.trim_start_matches('v').replace('-', "+");
117    let parts: Vec<&str> = v.split('.').collect();
118
119    if parts.len() == 1 {
120        format!("{}.0.0", parts[0])
121    } else if parts.len() == 2 {
122        format!("{}.{}.0", parts[0], parts[1])
123    } else if parts.len() > 3 {
124        format!(
125            "{}.{}.{}+{}",
126            parts[0],
127            parts[1],
128            parts[2],
129            parts[3..].join(".")
130        )
131    } else {
132        v.to_string()
133    }
134}
135
136#[derive(Error, Debug)]
137pub enum ZoiSolverError {
138    #[error("Dependency error: {0}")]
139    Dependency(String),
140    #[error("Version error: {0}")]
141    Version(String),
142    #[error("Anyhow error: {0}")]
143    Anyhow(#[from] anyhow::Error),
144    #[error("Other error: {0}")]
145    Other(String),
146}
147
148/// Adapts Zoi's package and registry model to the PubGrub SAT solver.
149///
150/// This is the most performance-critical part of the resolution logic.
151/// It implements the `DependencyProvider` trait, which allows the solver
152/// to "ask" Zoi:
153/// - "What versions are available for this package?"
154/// - "What are the dependencies for this specific version?"
155///
156/// The adapter handles querying the local SQLite index, remote registries,
157/// and project-local `zoi.lua` overrides in a unified way.
158pub struct ZoiDependencyProvider {
159    /// The initial set of direct dependencies requested by the user.
160    pub root_deps: FxHashMap<PkgName, Ranges<SemVersion>>,
161    /// The raw string sources (e.g. "#reg@repo/pkg@ver") provided to the CLI.
162    pub initial_sources: Vec<String>,
163    /// The target installation scope (User, System, Project).
164    pub scope: Option<types::Scope>,
165    /// Whether to suppress non-critical warnings during resolution.
166    pub quiet: bool,
167    /// Automatically accept default choices for interactive dependency options.
168    pub yes: bool,
169    /// Automatically include all optional dependencies.
170    pub all_optional: bool,
171    /// An in-memory cache of the registry used in 'mini' mode to avoid disk I/O.
172    pub mini_index: Option<zoi_resolver::mini_resolve::MiniRegistryIndex>,
173    /// The loaded `zoi.lua` or `zoi.yaml` project configuration, if applicable.
174    pub project_config: Option<zoi_project::config::ProjectConfig>,
175    /// Hard version constraints enforced by a project's lockfile or configuration.
176    pub pkgs_v2_constraints: HashMap<(String, String), String>,
177    /// Build type used for selecting typed build dependencies.
178    pub build_type: Option<String>,
179    /// Memoization cache mapping a package+version to its resolved dependency requirements.
180    /// The `RefCell` allows interior mutability since the PubGrub solver requires `&self`.
181    pub deps_cache:
182        RefCell<FxHashMap<(PkgName, SemVersion), FxHashMap<PkgName, Ranges<SemVersion>>>>,
183    /// Memoization cache storing the explicit options and optional dependencies chosen by the user.
184    pub chosen_cache:
185        RefCell<FxHashMap<(PkgName, SemVersion), (Vec<String>, Vec<String>, Vec<String>)>>,
186}
187
188/// Converts a SemVer requirement string into a PubGrub version range.
189///
190/// This function bridges the gap between the `semver` crate's flexible requirement
191/// strings and `pubgrub`'s mathematical version ranges.
192///
193/// Mapping Examples:
194/// - `1.2.3` -> `Ranges::singleton(1.2.3)` (Exact match)
195/// - `^1.2.3` -> `[1.2.3, 2.0.0)` (Caret: compatible updates)
196/// - `~1.2.3` -> `[1.2.3, 1.3.0)` (Tilde: patch-level updates)
197/// - `>=1.0.0, <2.0.0` -> `[1.0.0, 2.0.0)` (Intersection of ranges)
198///
199/// If a version string is not a valid SemVer requirement (e.g. a channel name like
200/// `@stable`), it is treated as a `Ranges::full()` to let Zoi's higher-level
201/// resolver handle the channel-to-version mapping.
202pub fn semver_to_range(req_str: &str) -> Ranges<SemVersion> {
203    let req_str_clean = req_str.trim_start_matches('@').trim_start_matches('v');
204
205    if let Ok(version) = SemVersion::parse(req_str_clean) {
206        return Ranges::singleton(version);
207    }
208
209    if let Ok(req) = semver::VersionReq::parse(req_str_clean) {
210        let mut range = Ranges::full();
211        for comparator in &req.comparators {
212            let v_str = format!(
213                "{}.{}.{}",
214                comparator.major,
215                comparator.minor.unwrap_or(0),
216                comparator.patch.unwrap_or(0)
217            );
218            let v = SemVersion {
219                epoch: 0,
220                v: Version {
221                    major: comparator.major,
222                    minor: comparator.minor.unwrap_or(0),
223                    patch: comparator.patch.unwrap_or(0),
224                    pre: comparator.pre.clone(),
225                    build: semver::BuildMetadata::EMPTY,
226                },
227                original: v_str,
228            };
229
230            let comp_range = match comparator.op {
231                semver::Op::Exact => Ranges::singleton(v),
232                semver::Op::Greater => Ranges::strictly_higher_than(v),
233                semver::Op::GreaterEq => Ranges::higher_than(v),
234                semver::Op::Less => Ranges::strictly_lower_than(v),
235                semver::Op::LessEq => Ranges::lower_than(v),
236                semver::Op::Tilde => {
237                    let next_minor_v = Version {
238                        major: comparator.major,
239                        minor: comparator.minor.unwrap_or(0) + 1,
240                        patch: 0,
241                        pre: semver::Prerelease::EMPTY,
242                        build: semver::BuildMetadata::EMPTY,
243                    };
244                    let next_minor = SemVersion {
245                        epoch: 0,
246                        v: next_minor_v.clone(),
247                        original: next_minor_v.to_string(),
248                    };
249                    Ranges::higher_than(v).intersection(&Ranges::strictly_lower_than(next_minor))
250                }
251                semver::Op::Caret => {
252                    let next_v = if comparator.major > 0 {
253                        Version {
254                            major: comparator.major + 1,
255                            minor: 0,
256                            patch: 0,
257                            pre: semver::Prerelease::EMPTY,
258                            build: semver::BuildMetadata::EMPTY,
259                        }
260                    } else if let Some(minor) = comparator.minor {
261                        if minor > 0 {
262                            Version {
263                                major: 0,
264                                minor: minor + 1,
265                                patch: 0,
266                                pre: semver::Prerelease::EMPTY,
267                                build: semver::BuildMetadata::EMPTY,
268                            }
269                        } else {
270                            Version {
271                                major: 0,
272                                minor: 0,
273                                patch: comparator.patch.unwrap_or(0) + 1,
274                                pre: semver::Prerelease::EMPTY,
275                                build: semver::BuildMetadata::EMPTY,
276                            }
277                        }
278                    } else {
279                        Version {
280                            major: 1,
281                            minor: 0,
282                            patch: 0,
283                            pre: semver::Prerelease::EMPTY,
284                            build: semver::BuildMetadata::EMPTY,
285                        }
286                    };
287                    let next = SemVersion {
288                        epoch: 0,
289                        v: next_v.clone(),
290                        original: next_v.to_string(),
291                    };
292                    Ranges::higher_than(v).intersection(&Ranges::strictly_lower_than(next))
293                }
294                _ => Ranges::full(),
295            };
296            range = range.intersection(&comp_range);
297        }
298        return range;
299    }
300
301    Ranges::full()
302}
303
304impl ZoiDependencyProvider {
305    pub fn new(
306        root_deps: FxHashMap<PkgName, Ranges<SemVersion>>,
307        initial_sources: Vec<String>,
308        scope: Option<types::Scope>,
309        quiet: bool,
310        yes: bool,
311        all_optional: bool,
312        project_config: Option<zoi_project::config::ProjectConfig>,
313        build_type: Option<String>,
314    ) -> Result<Self, anyhow::Error> {
315        let mini_index = if zoi_core::utils::is_mini_mode() {
316            Some(zoi_resolver::mini_resolve::fetch_registry_index()?)
317        } else {
318            None
319        };
320
321        let pkgs_v2_constraints = project_config
322            .as_ref()
323            .map(|config| {
324                config
325                    .pkgs_v2
326                    .iter()
327                    .filter_map(|(key, spec)| {
328                        spec.version
329                            .as_ref()
330                            .map(|v| (parse_pkgs_v2_key(key), v.clone()))
331                    })
332                    .collect::<HashMap<_, _>>()
333            })
334            .unwrap_or_default();
335
336        Ok(Self {
337            root_deps,
338            initial_sources,
339            scope,
340            quiet,
341            yes,
342            all_optional,
343            mini_index,
344            project_config,
345            pkgs_v2_constraints,
346            build_type,
347            deps_cache: RefCell::new(FxHashMap::default()),
348            chosen_cache: RefCell::new(FxHashMap::default()),
349        })
350    }
351
352    fn semver_to_range(&self, req_str: &str) -> Ranges<SemVersion> {
353        semver_to_range(req_str)
354    }
355
356    fn source_matches_package(&self, package: &PkgName, source: &str) -> bool {
357        if let Some(explicit_source) = &package.explicit_source {
358            let explicit_base = explicit_source
359                .rsplit_once('@')
360                .map(|(base, _)| base)
361                .unwrap_or(explicit_source.as_str());
362            let source_base = source
363                .rsplit_once('@')
364                .map(|(base, _)| base)
365                .unwrap_or(source);
366            return explicit_base == source_base;
367        }
368
369        let Ok(req) = resolve::parse_source_string(source) else {
370            return false;
371        };
372
373        if req.name != package.name || req.sub_package != package.sub_package {
374            return false;
375        }
376
377        let Ok(resolved_source) = resolve::resolve_source(source, self.scope, true, true) else {
378            return false;
379        };
380
381        resolved_source.repo_name.unwrap_or_default() == package.repo
382            && resolved_source
383                .registry_handle
384                .unwrap_or_else(|| "zoidberg".to_string())
385                == package.registry
386    }
387
388    pub fn get_versions(&self, package: &PkgName) -> Result<Vec<SemVersion>, ZoiSolverError> {
389        let mut all_versions = Vec::new();
390
391        if let Some(index) = &self.mini_index
392            && let Some(pkg_info) = index.packages.get(&package.name)
393            && let Ok(v) = SemVersion::parse(pkg_info.version.trim_start_matches('v'))
394        {
395            all_versions.push(v);
396        }
397
398        if let Ok(version_strings) =
399            db::get_all_versions(&package.registry, &package.name, &package.repo)
400        {
401            for v_str in version_strings {
402                if let Ok(v) = SemVersion::parse(&v_str) {
403                    all_versions.push(v);
404                }
405            }
406        }
407
408        for source in &self.initial_sources {
409            if self.source_matches_package(package, source)
410                && let Ok(req) = resolve::parse_source_string(source)
411                && let Some(v_spec) = req.version_spec
412                && let Ok(v) = SemVersion::parse(&v_spec)
413            {
414                all_versions.push(v);
415            }
416        }
417
418        let source_str = package.explicit_source.clone().unwrap_or_else(|| {
419            if let Some(sub) = &package.sub_package {
420                format!(
421                    "#{}@{}/{}:{}",
422                    package.registry, package.repo, package.name, sub
423                )
424            } else {
425                format!("#{}@{}/{}", package.registry, package.repo, package.name)
426            }
427        });
428
429        if let Ok(resolved) = resolve::resolve_source(&source_str, self.scope, true, true) {
430            let path_str = resolved.path.to_string_lossy();
431            if let Ok(pkg) = zoi_lua::parser::parse_lua_package(&path_str, None, self.scope, true) {
432                if let Some(v_str) = &pkg.version
433                    && let Ok(v) = SemVersion::parse(v_str)
434                {
435                    all_versions.push(v);
436                }
437                if let Some(versions_map) = &pkg.versions {
438                    for channel in versions_map.keys() {
439                        if let Ok(v_str) = resolve::resolve_channel(versions_map, channel)
440                            && let Ok(v) = SemVersion::parse(&v_str)
441                        {
442                            all_versions.push(v);
443                        }
444                    }
445                }
446            }
447        }
448
449        if all_versions.is_empty()
450            && let Ok(conn) = db::open_connection(&package.registry)
451        {
452            let mut stmt = conn
453                .prepare("SELECT version FROM packages WHERE name = ?1")
454                .map_err(|e| ZoiSolverError::Other(e.to_string()))?;
455            let rows = stmt
456                .query_map(params![package.name], |row| row.get::<_, Option<String>>(0))
457                .map_err(|e| ZoiSolverError::Other(e.to_string()))?;
458
459            for v_res in rows {
460                if let Ok(Some(v_str)) = v_res
461                    && let Ok(v) = SemVersion::parse(&v_str)
462                {
463                    all_versions.push(v);
464                }
465            }
466        }
467
468        all_versions.sort();
469        all_versions.dedup();
470
471        if let Some(version_spec) = self
472            .pkgs_v2_constraints
473            .get(&(package.repo.clone(), package.name.clone()))
474        {
475            let range = semver_to_range(version_spec);
476            all_versions.retain(|v| range.contains(v));
477        }
478
479        Ok(all_versions)
480    }
481}
482
483impl DependencyProvider for ZoiDependencyProvider {
484    type P = PkgName;
485    type V = SemVersion;
486    type VS = Ranges<SemVersion>;
487    type M = String;
488    type Priority = i32;
489    type Err = ZoiSolverError;
490
491    fn get_dependencies(
492        &self,
493        package: &Self::P,
494        version: &Self::V,
495    ) -> Result<Dependencies<Self::P, Self::VS, Self::M>, Self::Err> {
496        if package.name == "$root" {
497            return Ok(Dependencies::Available(
498                pubgrub::DependencyConstraints::from_iter(self.root_deps.clone()),
499            ));
500        }
501
502        let cache_key = (package.clone(), version.clone());
503        if let Some(cached) = self.deps_cache.borrow().get(&cache_key) {
504            return Ok(Dependencies::Available(
505                pubgrub::DependencyConstraints::from_iter(cached.clone()),
506            ));
507        }
508
509        let version_str = version.to_string();
510
511        let mut chosen_opts = Vec::new();
512        let mut chosen_opts_opt = Vec::new();
513
514        if let Some(config) = &self.project_config {
515            let packages_key = if let Some(sub) = &package.sub_package {
516                format!("@{}/{}:{}", package.repo, package.name, sub)
517            } else {
518                format!("@{}/{}", package.repo, package.name)
519            };
520
521            if let Some(spec) = config.pkgs_v2.get(&packages_key) {
522                if let Some(opts) = &spec.options {
523                    chosen_opts.extend(opts.clone());
524                }
525                if let Some(opt_optionals) = &spec.optionals {
526                    chosen_opts_opt.extend(opt_optionals.clone());
527                }
528            }
529        }
530
531        let mut package_deps: Option<types::DependenciesV2> = None;
532
533        if let Some(config) = &self.project_config {
534            let packages_key = if let Some(sub) = &package.sub_package {
535                format!("@{}/{}:{}", package.repo, package.name, sub)
536            } else {
537                format!("@{}/{}", package.repo, package.name)
538            };
539
540            if let Some(spec) = config.pkgs_v2.get(&packages_key)
541                && spec.version.as_ref().is_none_or(|v| {
542                    let range = semver_to_range(v);
543                    if let Ok(pv) = SemVersion::parse(&version_str) {
544                        range.contains(&pv) || v == &version_str
545                    } else {
546                        v == &version_str
547                    }
548                })
549                && let Some(v1_deps) = &spec.dependencies
550            {
551                package_deps = Some(v1_deps.resolve(
552                    &chosen_opts,
553                    &chosen_opts_opt,
554                    package.sub_package.as_deref(),
555                    self.all_optional,
556                    self.build_type.as_deref(),
557                ));
558            }
559        }
560
561        if package_deps.is_none() {
562            let dependencies_opt = db::get_package_dependencies(
563                &package.registry,
564                &package.name,
565                &version_str,
566                package.sub_package.as_deref(),
567                &package.repo,
568            )
569            .ok()
570            .flatten();
571
572            let v1_deps = if let Some(deps_json) = dependencies_opt
573                && !deps_json.is_empty()
574            {
575                serde_json::from_str::<types::Dependencies>(&deps_json).ok()
576            } else {
577                let source = package.explicit_source.clone().unwrap_or_else(|| {
578                    if let Some(sub) = &package.sub_package {
579                        format!(
580                            "#{}@{}/{}:{}@{}",
581                            package.registry, package.repo, package.name, sub, version_str
582                        )
583                    } else {
584                        format!(
585                            "#{}@{}/{}@{}",
586                            package.registry, package.repo, package.name, version_str
587                        )
588                    }
589                });
590
591                let pkg_res =
592                    resolve::resolve_package_and_version(&source, self.scope, self.quiet, self.yes);
593
594                match pkg_res {
595                    Ok((pkg, _, _, _, _, _, _)) => pkg.dependencies,
596                    Err(e) => {
597                        println!(
598                            "{} Failed to resolve source for deps: {}",
599                            "::".bold().red(),
600                            e
601                        );
602                        None
603                    }
604                }
605            };
606
607            package_deps = v1_deps.map(|d| {
608                d.resolve(
609                    &chosen_opts,
610                    &chosen_opts_opt,
611                    package.sub_package.as_deref(),
612                    self.all_optional,
613                    self.build_type.as_deref(),
614                )
615            });
616        }
617
618        let mut deps = FxHashMap::default();
619        let mut all_req = Vec::new();
620
621        if let Some(dependencies) = package_deps {
622            let mut groups = Vec::new();
623            groups.push(&dependencies.runtime);
624            for b in &dependencies.build {
625                groups.push(&b.packages);
626            }
627
628            for group_pkgs in groups {
629                for dep_str in group_pkgs {
630                    let dep_req = zoi_deps::parse_dependency_string(dep_str).map_err(|e| {
631                        ZoiSolverError::Dependency(format!("parse fail for '{}': {}", dep_str, e))
632                    })?;
633
634                    if dep_req.manager == "zoi" {
635                        let req = match resolve::parse_source_string(dep_req.package) {
636                            Ok(r) => r,
637                            Err(e) => {
638                                println!(
639                                    "{} Dependency parse failed for '{}': {}",
640                                    "::".bold().red(),
641                                    dep_req.package,
642                                    e
643                                );
644                                return Err(ZoiSolverError::Dependency(format!(
645                                    "parse source fail for '{}': {}",
646                                    dep_req.package, e
647                                )));
648                            }
649                        };
650
651                        let resolved_dep = match resolve::resolve_source(
652                            dep_req.package,
653                            self.scope,
654                            false,
655                            self.yes,
656                        ) {
657                            Ok(r) => r,
658                            Err(e) => {
659                                println!(
660                                    "{} Dependency resolution failed for '{}': {}",
661                                    "::".bold().red(),
662                                    dep_req.package,
663                                    e
664                                );
665                                return Err(ZoiSolverError::Dependency(format!(
666                                    "resolve fail for '{}': {}",
667                                    dep_req.package, e
668                                )));
669                            }
670                        };
671
672                        let dep_name = PkgName {
673                            name: req.name,
674                            sub_package: req.sub_package,
675                            repo: resolved_dep.repo_name.clone().unwrap_or_default(),
676                            registry: resolved_dep
677                                .registry_handle
678                                .clone()
679                                .unwrap_or_else(|| "zoidberg".to_string()),
680                            explicit_source: matches!(
681                                resolved_dep.source_type,
682                                zoi_core::types::SourceType::LocalFile
683                                    | zoi_core::types::SourceType::Url
684                                    | zoi_core::types::SourceType::GitRepo(_)
685                            )
686                            .then(|| dep_req.package.to_string()),
687                        };
688
689                        let range = if req.version_spec.is_some() {
690                            match resolve::resolve_requested_version_spec(
691                                dep_req.package,
692                                self.scope,
693                                false,
694                                true,
695                            ) {
696                                Ok(Some(v)) => self.semver_to_range(&v),
697                                Ok(None) => Ranges::full(),
698                                Err(e) => {
699                                    println!(
700                                        "{} Version resolution failed for '{}': {}",
701                                        "::".bold().red(),
702                                        dep_req.package,
703                                        e
704                                    );
705                                    return Err(ZoiSolverError::Dependency(format!(
706                                        "version resolve fail for '{}': {}",
707                                        dep_req.package, e
708                                    )));
709                                }
710                            }
711                        } else {
712                            Ranges::full()
713                        };
714
715                        deps.insert(dep_name, range);
716                    }
717                }
718            }
719            all_req = dependencies.runtime.clone();
720        }
721
722        self.deps_cache
723            .borrow_mut()
724            .insert(cache_key.clone(), deps.clone());
725        self.chosen_cache
726            .borrow_mut()
727            .insert(cache_key, (chosen_opts, chosen_opts_opt, all_req));
728        Ok(Dependencies::Available(
729            pubgrub::DependencyConstraints::from_iter(deps),
730        ))
731    }
732
733    fn choose_version(
734        &self,
735        package: &Self::P,
736        versions: &pubgrub::Ranges<Self::V>,
737    ) -> Result<Option<Self::V>, Self::Err> {
738        if package.name == "$root" {
739            return Ok(Some(SemVersion {
740                epoch: 0,
741                v: Version::new(0, 0, 0),
742                original: "0.0.0".to_string(),
743            }));
744        }
745        let all_versions = self.get_versions(package)?;
746        let best_version = all_versions.into_iter().rfind(|v| versions.contains(v));
747        Ok(best_version)
748    }
749
750    fn prioritize(
751        &self,
752        _package: &Self::P,
753        _range: &Self::VS,
754        _stats: &pubgrub::PackageResolutionStatistics,
755    ) -> Self::Priority {
756        0
757    }
758}