Skip to main content

miden_debug_engine/
registry.rs

1use std::{
2    collections::BTreeMap,
3    format,
4    path::{Path, PathBuf},
5    sync::Arc,
6};
7
8use miden_assembly_syntax::{
9    Report,
10    diagnostics::{Diagnostic, miette},
11};
12use miden_mast_package::Package;
13use miden_package_registry::{
14    PackageCache, PackageId, PackageIndex, PackageProvider, PackageRecord, PackageRegistry,
15    PackageStore, PackageVersions,
16};
17use miden_project::VersionRequirement;
18use rustc_hash::FxHashMap;
19
20use crate::LinkLibrary;
21
22#[derive(Debug, thiserror::Error, Diagnostic)]
23enum InstallPackageError {
24    #[error("package {package}@{version} is already registered under a different digest")]
25    AlreadyInstalledWithDifferentDigest {
26        package: PackageId,
27        version: miden_project::Version,
28    },
29}
30
31/// The in-memory package registry used by the compiler
32///
33/// This is initialized per-session, or on an as-needed basis.
34///
35/// It can be constructed in various ways, but the recommended way to use it is
36/// [HybridPackageRegistry::new], which loads packages from the local filesystem registry (if
37/// available), and adds in any libraries requested explicitly via `-l`.
38pub struct HybridPackageRegistry {
39    packages: FxHashMap<PackageId, PackageVersions>,
40    artifacts: FxHashMap<PackageId, BTreeMap<miden_package_registry::Version, Arc<Package>>>,
41}
42
43impl HybridPackageRegistry {
44    /// Get an empty, uninitialized registry
45    pub fn empty() -> Self {
46        Self {
47            packages: Default::default(),
48            artifacts: Default::default(),
49        }
50    }
51
52    /// Get a new instance of the registry, using the current compiler options
53    pub fn new(
54        sysroot: Option<&Path>,
55        search_paths: &[PathBuf],
56        link_libraries: &[LinkLibrary],
57    ) -> Result<Self, Report> {
58        // Load system libraries
59        let mut registry = if let Some(sysroot) = sysroot {
60            Self::from_local_registry(sysroot)?
61        } else {
62            Self::empty()
63        };
64
65        // Load link libraries
66        for lib in link_libraries {
67            let package = lib.load(search_paths, &mut registry)?;
68            match registry.install_if_missing(package) {
69                Ok(_) => (),
70                // Ignore duplicates when initializing the registry
71                Err(InstallPackageError::AlreadyInstalledWithDifferentDigest { .. }) => (),
72            }
73        }
74
75        Ok(registry)
76    }
77
78    /// Get a new instance of the registry seeded with packages available in the local filesystem-
79    /// based package store.
80    ///
81    /// This returns an error if `--sysroot` was not provided/set.
82    pub fn from_local_registry(sysroot: &Path) -> Result<Self, Report> {
83        let lib_dir = sysroot.join("lib");
84        let entries = lib_dir.read_dir().map_err(|err| {
85            Report::msg(format!("cannot read from sysroot ({}): {err}", lib_dir.display()))
86        })?;
87
88        let mut registry = Self::empty();
89        for entry in entries {
90            let Ok(entry) = entry else {
91                continue;
92            };
93            let path = entry.path();
94            if path.extension().is_none_or(|ext| !ext.eq_ignore_ascii_case("masp")) {
95                continue;
96            }
97
98            let package = crate::linker::load_package_from_path(&path)?;
99            match registry.install_if_missing(package) {
100                Ok(_) => (),
101                // Ignore duplicates when initializing the registry
102                Err(InstallPackageError::AlreadyInstalledWithDifferentDigest { .. }) => (),
103            }
104        }
105
106        Ok(registry)
107    }
108
109    pub fn all(&self) -> impl IntoIterator<Item = Arc<Package>> {
110        self.artifacts.values().flat_map(|versions| versions.values().cloned())
111    }
112
113    fn install_if_missing(
114        &mut self,
115        package: Arc<Package>,
116    ) -> Result<miden_project::Version, InstallPackageError> {
117        use std::collections::{btree_map::Entry as BTreeMapEntry, hash_map::Entry};
118
119        let version =
120            miden_project::Version::new(package.version.clone(), package.dependency_commitment());
121        log::trace!(target: "package-registry", "preparing to install package {}@{version}", package.name);
122        let record = PackageRecord::new(
123            version.clone(),
124            package.manifest.dependencies().map(|dep| {
125                (
126                    dep.name.clone(),
127                    VersionRequirement::Exact(miden_project::Version::new(
128                        dep.version.clone(),
129                        dep.digest,
130                    )),
131                )
132            }),
133        );
134        match self.packages.entry(package.name.clone()) {
135            Entry::Occupied(mut entry) => {
136                let versions = entry.get_mut();
137                match versions.entry(package.version.clone()) {
138                    BTreeMapEntry::Occupied(mut prev) => {
139                        let prev_digest = prev.get().digest().copied();
140                        if prev_digest.is_none_or(|prev_digest| {
141                            prev_digest == package.dependency_commitment()
142                        }) {
143                            prev.insert(record);
144                        } else {
145                            log::trace!(target: "package-registry", "package already installed: {}@{version}", package.name);
146                            return Err(InstallPackageError::AlreadyInstalledWithDifferentDigest {
147                                package: package.name.clone(),
148                                version,
149                            });
150                        }
151                    }
152                    BTreeMapEntry::Vacant(entry) => {
153                        entry.insert(record);
154                    }
155                }
156            }
157            Entry::Vacant(entry) => {
158                entry.insert([(package.version.clone(), record)].into_iter().collect());
159            }
160        }
161
162        log::trace!(target: "package-registry", "installed {}@{version}", package.name);
163
164        self.artifacts
165            .entry(package.name.clone())
166            .or_default()
167            .insert(version.clone(), package);
168
169        Ok(version)
170    }
171}
172
173impl HybridPackageRegistry {
174    fn insert_record(&mut self, id: PackageId, record: PackageRecord) {
175        self.packages
176            .entry(id)
177            .or_default()
178            .insert(record.semantic_version().clone(), record);
179    }
180}
181
182impl PackageRegistry for HybridPackageRegistry {
183    fn available_versions(&self, package: &PackageId) -> Option<&PackageVersions> {
184        self.packages.get(package)
185    }
186}
187
188impl PackageIndex for HybridPackageRegistry {
189    type Error = Report;
190
191    fn register(&mut self, name: PackageId, record: PackageRecord) -> Result<(), Self::Error> {
192        if self.is_semver_available(&name, record.semantic_version()) {
193            return Err(Report::msg(format!(
194                "cannot register {name}: version {} is already registered",
195                record.semantic_version()
196            )));
197        }
198        self.insert_record(name, record);
199        Ok(())
200    }
201}
202
203impl PackageProvider for HybridPackageRegistry {
204    fn load_package(
205        &self,
206        package: &PackageId,
207        version: &miden_project::Version,
208    ) -> Result<Arc<Package>, Report> {
209        let found = self.artifacts.get(package).and_then(|versions| versions.get(&version.version));
210        match found {
211            Some(artifact) if version.digest != Some(artifact.dependency_commitment()) => {
212                Err(Report::msg(format!(
213                    "cannot load {package}@{version}: a specific digest was requested, but \
214                     differs from the available version"
215                )))
216            }
217            Some(artifact) => Ok(Arc::clone(artifact)),
218            None => Err(Report::msg(format!(
219                "cannot load {package}@{version}: no such package available",
220            ))),
221        }
222    }
223}
224
225impl PackageCache for HybridPackageRegistry {
226    type Error = Report;
227
228    fn cache_package(
229        &mut self,
230        package: Arc<Package>,
231    ) -> Result<miden_project::Version, Self::Error> {
232        self.install_if_missing(package).map_err(Report::from)
233    }
234}
235
236impl PackageStore for HybridPackageRegistry {
237    fn publish_package(
238        &mut self,
239        package: Arc<Package>,
240    ) -> Result<miden_project::Version, Self::Error> {
241        self.install_if_missing(package).map_err(Report::from)
242    }
243}