miden_debug_engine/
registry.rs1use 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
31pub struct HybridPackageRegistry {
39 packages: FxHashMap<PackageId, PackageVersions>,
40 artifacts: FxHashMap<PackageId, BTreeMap<miden_package_registry::Version, Arc<Package>>>,
41}
42
43impl HybridPackageRegistry {
44 pub fn empty() -> Self {
46 Self {
47 packages: Default::default(),
48 artifacts: Default::default(),
49 }
50 }
51
52 pub fn new(
54 sysroot: Option<&Path>,
55 search_paths: &[PathBuf],
56 link_libraries: &[LinkLibrary],
57 ) -> Result<Self, Report> {
58 let mut registry = if let Some(sysroot) = sysroot {
60 Self::from_local_registry(sysroot)?
61 } else {
62 Self::empty()
63 };
64
65 for lib in link_libraries {
67 let package = lib.load(search_paths, &mut registry)?;
68 match registry.install_if_missing(package) {
69 Ok(_) => (),
70 Err(InstallPackageError::AlreadyInstalledWithDifferentDigest { .. }) => (),
72 }
73 }
74
75 Ok(registry)
76 }
77
78 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 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}