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 = miden_project::Version::new(package.version.clone(), package.digest());
120        log::trace!(target: "package-registry", "preparing to install package {}@{version}", package.name);
121        let record = PackageRecord::new(
122            version.clone(),
123            package.manifest.dependencies().map(|dep| {
124                (
125                    dep.name.clone(),
126                    VersionRequirement::Exact(miden_project::Version::new(
127                        dep.version.clone(),
128                        dep.digest,
129                    )),
130                )
131            }),
132        );
133        match self.packages.entry(package.name.clone()) {
134            Entry::Occupied(mut entry) => {
135                let versions = entry.get_mut();
136                match versions.entry(package.version.clone()) {
137                    BTreeMapEntry::Occupied(mut prev) => {
138                        let prev_digest = prev.get().digest().copied();
139                        if prev_digest.is_none_or(|prev_digest| prev_digest == package.digest()) {
140                            prev.insert(record);
141                        } else {
142                            log::trace!(target: "package-registry", "package already installed: {}@{version}", package.name);
143                            return Err(InstallPackageError::AlreadyInstalledWithDifferentDigest {
144                                package: package.name.clone(),
145                                version,
146                            });
147                        }
148                    }
149                    BTreeMapEntry::Vacant(entry) => {
150                        entry.insert(record);
151                    }
152                }
153            }
154            Entry::Vacant(entry) => {
155                entry.insert([(package.version.clone(), record)].into_iter().collect());
156            }
157        }
158
159        log::trace!(target: "package-registry", "installed {}@{version}", package.name);
160
161        self.artifacts
162            .entry(package.name.clone())
163            .or_default()
164            .insert(version.clone(), package);
165
166        Ok(version)
167    }
168}
169
170impl HybridPackageRegistry {
171    fn insert_record(&mut self, id: PackageId, record: PackageRecord) {
172        self.packages
173            .entry(id)
174            .or_default()
175            .insert(record.semantic_version().clone(), record);
176    }
177}
178
179impl PackageRegistry for HybridPackageRegistry {
180    fn available_versions(&self, package: &PackageId) -> Option<&PackageVersions> {
181        self.packages.get(package)
182    }
183}
184
185impl PackageIndex for HybridPackageRegistry {
186    type Error = Report;
187
188    fn register(&mut self, name: PackageId, record: PackageRecord) -> Result<(), Self::Error> {
189        if self.is_semver_available(&name, record.semantic_version()) {
190            return Err(Report::msg(format!(
191                "cannot register {name}: version {} is already registered",
192                record.semantic_version()
193            )));
194        }
195        self.insert_record(name, record);
196        Ok(())
197    }
198}
199
200impl PackageProvider for HybridPackageRegistry {
201    fn load_package(
202        &self,
203        package: &PackageId,
204        version: &miden_project::Version,
205    ) -> Result<Arc<Package>, Report> {
206        let found = self.artifacts.get(package).and_then(|versions| versions.get(&version.version));
207        match found {
208            Some(artifact) if version.digest != Some(artifact.digest()) => {
209                Err(Report::msg(format!(
210                    "cannot load {package}@{version}: a specific digest was requested, but \
211                     differs from the available version"
212                )))
213            }
214            Some(artifact) => Ok(Arc::clone(artifact)),
215            None => Err(Report::msg(format!(
216                "cannot load {package}@{version}: no such package available",
217            ))),
218        }
219    }
220}
221
222impl PackageCache for HybridPackageRegistry {
223    type Error = Report;
224
225    fn cache_package(
226        &mut self,
227        package: Arc<Package>,
228    ) -> Result<miden_project::Version, Self::Error> {
229        self.install_if_missing(package).map_err(Report::from)
230    }
231}
232
233impl PackageStore for HybridPackageRegistry {
234    fn publish_package(
235        &mut self,
236        package: Arc<Package>,
237    ) -> Result<miden_project::Version, Self::Error> {
238        self.install_if_missing(package).map_err(Report::from)
239    }
240}