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 =
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}