miden_debug_engine/
registry.rs1use alloc::{collections::BTreeMap, sync::Arc};
2#[cfg(feature = "std")]
3use std::path::{Path, PathBuf};
4
5use miden_assembly_syntax::{
6 Report,
7 diagnostics::{Diagnostic, miette},
8};
9use miden_mast_package::Package;
10use miden_package_registry::{
11 PackageCache, PackageId, PackageIndex, PackageProvider, PackageRecord, PackageRegistry,
12 PackageStore, PackageVersions, VersionRequirement,
13};
14
15type FxHashMap<K, V> = hashbrown::HashMap<K, V, rustc_hash::FxBuildHasher>;
16
17#[cfg(feature = "std")]
18use crate::LinkLibrary;
19
20#[derive(Debug, thiserror::Error, Diagnostic)]
21enum InstallPackageError {
22 #[error("package {package}@{version} is already registered under a different digest")]
23 AlreadyInstalledWithDifferentDigest {
24 package: PackageId,
25 version: miden_package_registry::Version,
26 },
27}
28
29pub struct HybridPackageRegistry {
37 packages: FxHashMap<PackageId, PackageVersions>,
38 artifacts: FxHashMap<PackageId, BTreeMap<miden_package_registry::Version, Arc<Package>>>,
39}
40
41impl HybridPackageRegistry {
42 pub fn empty() -> Self {
44 Self {
45 packages: Default::default(),
46 artifacts: Default::default(),
47 }
48 }
49
50 #[cfg(feature = "std")]
52 pub fn new(
53 sysroot: Option<&Path>,
54 search_paths: &[PathBuf],
55 link_libraries: &[LinkLibrary],
56 ) -> Result<Self, Report> {
57 let mut registry = if let Some(sysroot) = sysroot {
59 Self::from_local_registry(sysroot)?
60 } else {
61 Self::empty()
62 };
63
64 for lib in link_libraries {
66 let package = lib.load(search_paths)?;
67 match registry.install_if_missing(package) {
68 Ok(_) => (),
69 Err(InstallPackageError::AlreadyInstalledWithDifferentDigest { .. }) => (),
71 }
72 }
73
74 Ok(registry)
75 }
76
77 #[cfg(feature = "std")]
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::package::load_package_from_path(&path)?;
99 match registry.install_if_missing(package) {
100 Ok(_) => (),
101 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_package_registry::Version, InstallPackageError> {
117 use alloc::collections::btree_map::Entry as BTreeMapEntry;
118
119 use hashbrown::hash_map::Entry;
120
121 let version = miden_package_registry::Version::new(
122 package.version.clone(),
123 package.dependency_commitment(),
124 );
125 log::trace!(target: "package-registry", "preparing to install package {}@{version}", package.name);
126 let record = PackageRecord::new(
127 version.clone(),
128 package.manifest.dependencies().map(|dep| {
129 (
130 dep.name.clone(),
131 VersionRequirement::Exact(miden_package_registry::Version::new(
132 dep.version.clone(),
133 dep.digest,
134 )),
135 )
136 }),
137 );
138 match self.packages.entry(package.name.clone()) {
139 Entry::Occupied(mut entry) => {
140 let versions = entry.get_mut();
141 match versions.entry(package.version.clone()) {
142 BTreeMapEntry::Occupied(mut prev) => {
143 let prev_digest = prev.get().digest().copied();
144 if prev_digest.is_none_or(|prev_digest| {
145 prev_digest == package.dependency_commitment()
146 }) {
147 prev.insert(record);
148 } else {
149 log::trace!(target: "package-registry", "package already installed: {}@{version}", package.name);
150 return Err(InstallPackageError::AlreadyInstalledWithDifferentDigest {
151 package: package.name.clone(),
152 version,
153 });
154 }
155 }
156 BTreeMapEntry::Vacant(entry) => {
157 entry.insert(record);
158 }
159 }
160 }
161 Entry::Vacant(entry) => {
162 entry.insert([(package.version.clone(), record)].into_iter().collect());
163 }
164 }
165
166 log::trace!(target: "package-registry", "installed {}@{version}", package.name);
167
168 self.artifacts
169 .entry(package.name.clone())
170 .or_default()
171 .insert(version.clone(), package);
172
173 Ok(version)
174 }
175}
176
177impl HybridPackageRegistry {
178 fn insert_record(&mut self, id: PackageId, record: PackageRecord) {
179 self.packages
180 .entry(id)
181 .or_default()
182 .insert(record.semantic_version().clone(), record);
183 }
184}
185
186impl PackageRegistry for HybridPackageRegistry {
187 fn available_versions(&self, package: &PackageId) -> Option<&PackageVersions> {
188 self.packages.get(package)
189 }
190}
191
192impl PackageIndex for HybridPackageRegistry {
193 type Error = Report;
194
195 fn register(&mut self, name: PackageId, record: PackageRecord) -> Result<(), Self::Error> {
196 if self.is_semver_available(&name, record.semantic_version()) {
197 return Err(Report::msg(format!(
198 "cannot register {name}: version {} is already registered",
199 record.semantic_version()
200 )));
201 }
202 self.insert_record(name, record);
203 Ok(())
204 }
205}
206
207impl PackageProvider for HybridPackageRegistry {
208 fn load_package(
209 &self,
210 package: &PackageId,
211 version: &miden_package_registry::Version,
212 ) -> Result<Arc<Package>, Report> {
213 let found = self.artifacts.get(package).and_then(|versions| versions.get(&version.version));
214 match found {
215 Some(artifact) if version.digest != Some(artifact.dependency_commitment()) => {
216 Err(Report::msg(format!(
217 "cannot load {package}@{version}: a specific digest was requested, but \
218 differs from the available version"
219 )))
220 }
221 Some(artifact) => Ok(Arc::clone(artifact)),
222 None => Err(Report::msg(format!(
223 "cannot load {package}@{version}: no such package available",
224 ))),
225 }
226 }
227}
228
229impl PackageCache for HybridPackageRegistry {
230 type Error = Report;
231
232 fn cache_package(
233 &mut self,
234 package: Arc<Package>,
235 ) -> Result<miden_package_registry::Version, Self::Error> {
236 self.install_if_missing(package).map_err(Report::from)
237 }
238}
239
240impl PackageStore for HybridPackageRegistry {
241 fn publish_package(
242 &mut self,
243 package: Arc<Package>,
244 ) -> Result<miden_package_registry::Version, Self::Error> {
245 self.install_if_missing(package).map_err(Report::from)
246 }
247}