Skip to main content

miden_project/dependencies/resolver/
index.rs

1use alloc::collections::BTreeMap;
2use core::borrow::Borrow;
3
4use pubgrub::VersionSet as _;
5
6use super::*;
7use crate::{Package, Version, VersionRequirement};
8
9/// A type alias for an ordered map of packages and the set of versions that can satisfy the
10/// requirement for that package.
11pub type PackageRequirements = BTreeMap<PackageId, VersionSet>;
12
13/// [PackageIndex] is used as an in-memory database of package information needed for dependency
14/// resolution for Miden projects. It records and can provide information about:
15///
16/// * All packages for which there is at least one version available
17/// * All versions of a package that are available
18/// * The dependencies for each version of a package, and their version requirements.
19///
20/// Additionally, [PackageIndex] implements version selection and dependency resolution for packages
21/// in the index. Since this functionality is central to any tooling around Miden projects, we
22/// implement it here, rather than push the responsibility onto downstream crates.
23///
24/// This structure _does not_ provide any of the following:
25///
26/// * Package metadata beyond version and dependency requirements
27/// * MAST of indexed packages
28/// * A guarantee that indexed packages actually exist or that their sources/MAST can be obtained
29///
30/// It is up to downstream consumers of the index to construct it and populate it with packages
31/// known to them, such that packages selected by [`PackageResolver::resolve`] can be resolved to
32/// relevant artifacts, e.g. an assembled package, Miden project sources, etc.
33#[derive(Default)]
34pub struct PackageIndex {
35    packages: BTreeMap<PackageId, BTreeMap<Version, PackageRequirements>>,
36}
37
38/// Construction
39impl PackageIndex {
40    /// Register `package` in the index.
41    ///
42    /// This is essentially a convenience wrapper around [`PackageIndex::insert`] for the common
43    /// case of populating the index with package information loaded from a Miden project.
44    pub fn register(&mut self, package: &Package) {
45        let name = PackageId::from(package.name().into_inner());
46        let version = package.version().into_inner().clone();
47        let version = Version::from(version);
48        let deps = package.dependencies().iter().map(|dep| {
49            let name = PackageId::from(dep.name().clone());
50            let req = match dep.required_version() {
51                None => VersionSet::full(),
52                Some(req) => VersionSet::from(req),
53            };
54            (name, req)
55        });
56
57        self.insert(name, version, deps);
58    }
59
60    /// Insert a new entry in the index for `name` and `version`, with `dependencies` providing the
61    /// set of version constraints for the package dependencies.
62    pub fn insert<D, P, V>(&mut self, name: impl Into<PackageId>, version: Version, dependencies: D)
63    where
64        D: IntoIterator<Item = (P, V)>,
65        P: Into<PackageId>,
66        V: Into<VersionSet>,
67    {
68        let name = name.into();
69        let deps = dependencies.into_iter().map(|(k, v)| (k.into(), v.into())).collect();
70
71        self.insert_or_refine(name, version, deps);
72    }
73
74    /// Add or update an entry in the index for `name` and `version`, as follows:
75    ///
76    /// * If there are no versions recorded for `name`, initialize the index for `name` with the
77    ///   provided `version` and `deps.
78    /// * If there are existing versions for `name`, but `version` is new, and an entry for
79    ///   `version` with `deps`.
80    /// * If there is an existing entry for `version`:
81    ///   * Overwrite the previous dependency map with `deps` for the entry
82    ///   * If the entry in the index does not have an associated package digest, but one was given
83    ///     with `version`, then update the entry in the index to reflect the more precise version
84    ///     data.
85    ///   * If the entry in the index has an associated package digest, but `version` does not, then
86    ///     ignore the insertion entirely, in order to preserve the dependencies that were recorded
87    ///     with the more precise version.
88    ///
89    /// In effect, this method ensures that we only expand or refine the index.
90    fn insert_or_refine(&mut self, name: PackageId, version: Version, deps: PackageRequirements) {
91        use alloc::collections::btree_map::Entry;
92
93        match self.packages.entry(name) {
94            Entry::Vacant(entry) => {
95                let versions = BTreeMap::from_iter([(version, deps)]);
96                entry.insert(versions);
97            },
98            Entry::Occupied(mut entry) => {
99                let versions = entry.get_mut();
100                match versions.entry(version.clone()) {
101                    Entry::Vacant(entry) => {
102                        entry.insert(deps);
103                    },
104                    Entry::Occupied(mut entry) => {
105                        // Do not overwrite an existing entry if that entry has an associated
106                        // package digest, so as to preserve the more precise version information.
107                        if entry.key().digest.is_none() {
108                            // If `version` has an associated package digest, then we need to
109                            // recreate the entry in the index to ensure that the more precise
110                            // version is used
111                            if version.digest.is_some() {
112                                let _ = entry.remove_entry();
113                                versions.insert(version, deps);
114                            } else {
115                                // Otherwise, treat this like a normal `insert` and overwrite the
116                                // dependencies associated with this version entry.
117                                entry.insert(deps);
118                            }
119                        }
120                    },
121                }
122            },
123        }
124    }
125}
126
127/// Queries
128impl PackageIndex {
129    /// Returns true if package `name` has any available versions in the index.
130    #[inline(always)]
131    pub fn is_available<Q>(&self, name: &Q) -> bool
132    where
133        PackageId: Borrow<Q>,
134        Q: Ord + ?Sized,
135    {
136        self.packages.contains_key(name)
137    }
138
139    /// Returns true if package `name` with `version` is available in the index.
140    #[inline(always)]
141    pub fn is_version_available<Q>(&self, name: &Q, version: &Version) -> bool
142    where
143        PackageId: Borrow<Q>,
144        Q: Ord + ?Sized,
145    {
146        self.packages
147            .get(name)
148            .map(|versions| versions.contains_key(version))
149            .unwrap_or(false)
150    }
151
152    /// Search for a version of package `name` that matches `requirement`, and return its
153    /// dependencies.
154    ///
155    /// The version selected by this method will be the latest one which satisfies `requirement`.
156    #[inline(always)]
157    pub fn find<Q>(
158        &self,
159        name: &Q,
160        requirement: &VersionRequirement,
161    ) -> Option<&PackageRequirements>
162    where
163        PackageId: Borrow<Q>,
164        Q: Ord + ?Sized,
165    {
166        self.packages.get(name).and_then(|versions| {
167            versions
168                .iter()
169                .rev()
170                .find_map(|(v, deps)| if v.satisfies(requirement) { Some(deps) } else { None })
171        })
172    }
173
174    /// Get the set of versions associated with `name` in the index.
175    #[inline(always)]
176    pub(crate) fn get<Q>(&self, name: &Q) -> Option<&BTreeMap<Version, PackageRequirements>>
177    where
178        PackageId: Borrow<Q>,
179        Q: Ord + ?Sized,
180    {
181        self.packages.get(name)
182    }
183
184    /// Get an iterator over all versions of `package` available in the index, in descending order.
185    ///
186    /// NOTE: Descending order here is determined by the `Ord` implementation of [Version], which
187    /// orders versions by their semantic versioning scheme, and disambiguates using package digests
188    /// when known.
189    pub fn available_versions<Q>(&self, package: &Q) -> impl Iterator<Item = &Version>
190    where
191        PackageId: Borrow<Q>,
192        Q: Ord + ?Sized,
193    {
194        self.packages.get(package).into_iter().flat_map(|k| k.keys()).rev()
195    }
196
197    /// Get an iterator over all versions of `package` that satisfy `requirement.
198    pub fn list_versions<'a, Q>(
199        &'a self,
200        package: &'a Q,
201        requirement: &'a VersionRequirement,
202    ) -> impl Iterator<Item = &'a Version> + 'a
203    where
204        PackageId: Borrow<Q>,
205        Q: Ord + ?Sized,
206    {
207        self.available_versions(package).filter(|v| v.satisfies(requirement))
208    }
209}
210
211impl<'a, V, D> FromIterator<(&'a str, V)> for PackageIndex
212where
213    D: IntoIterator<Item = (&'a str, VersionSet)>,
214    V: IntoIterator<Item = (Version, D)>,
215{
216    fn from_iter<I>(iter: I) -> Self
217    where
218        I: IntoIterator<Item = (&'a str, V)>,
219    {
220        let mut index = Self::default();
221        for (name, versions) in iter {
222            let name = PackageId::from(name);
223            for (version, deps) in versions {
224                let deps = deps.into_iter().map(|(name, vs)| (PackageId::from(name), vs));
225                index.insert(name.clone(), version, deps);
226            }
227        }
228        index
229    }
230}