1#![no_std]
2
3#[macro_use]
4extern crate alloc;
5
6#[cfg(any(test, feature = "std"))]
7extern crate std;
8
9#[cfg(feature = "resolver")]
10mod resolver;
11mod version;
12mod version_requirement;
13
14use alloc::{collections::BTreeMap, string::String, sync::Arc};
15use core::fmt;
16
17use miden_assembly_syntax::Report;
18pub use miden_assembly_syntax::{
19 debuginfo::Span,
20 semver,
21 semver::{Version as SemVer, VersionReq},
22};
23pub use miden_core::Word;
24use miden_mast_package::Package as MastPackage;
25pub use miden_mast_package::PackageId;
26#[cfg(feature = "serde")]
27use serde::{Deserialize, Serialize};
28
29#[cfg(feature = "resolver")]
30pub use self::resolver::{
31 DependencyResolutionError, InMemoryPackageRegistry, PackagePriority, PackageResolver,
32 VersionSet,
33};
34pub use self::{
35 version::{InvalidVersionError, SemVerError, Version},
36 version_requirement::VersionRequirement,
37};
38
39pub type PackageRequirements = BTreeMap<PackageId, VersionRequirement>;
41
42#[derive(Debug, Clone, PartialEq, Eq)]
44#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
45pub struct PackageRecord {
46 version: Version,
48 description: Option<Arc<str>>,
50 dependencies: PackageRequirements,
52}
53
54impl PackageRecord {
55 pub fn new(
57 version: Version,
58 dependencies: impl IntoIterator<Item = (PackageId, VersionRequirement)>,
59 ) -> Self {
60 Self {
61 version,
62 description: None,
63 dependencies: dependencies.into_iter().collect(),
64 }
65 }
66
67 pub fn with_description(mut self, description: impl Into<Arc<str>>) -> Self {
69 self.description = Some(description.into());
70 self
71 }
72
73 pub fn version(&self) -> &Version {
75 &self.version
76 }
77
78 pub fn semantic_version(&self) -> &SemVer {
80 &self.version.version
81 }
82
83 pub fn digest(&self) -> Option<&Word> {
85 self.version.digest.as_ref()
86 }
87
88 pub fn description(&self) -> Option<&Arc<str>> {
90 self.description.as_ref()
91 }
92
93 pub fn dependencies(&self) -> &PackageRequirements {
95 &self.dependencies
96 }
97}
98
99pub type PackageVersions = BTreeMap<SemVer, PackageRecord>;
104
105pub trait PackageRegistry {
107 fn available_versions(&self, package: &PackageId) -> Option<&PackageVersions>;
109
110 fn is_available(&self, package: &PackageId) -> bool {
112 self.available_versions(package).is_some_and(|versions| !versions.is_empty())
113 }
114
115 fn is_version_available(&self, package: &PackageId, version: &Version) -> bool {
117 self.get_by_version(package, version).is_some()
118 }
119
120 fn is_semver_available(&self, package: &PackageId, version: &SemVer) -> bool {
122 self.get_by_semver(package, version).is_some()
123 }
124
125 fn get_by_version(&self, package: &PackageId, version: &Version) -> Option<&PackageRecord> {
127 let record = self.available_versions(package)?.get(&version.version)?;
128 match version.digest.as_ref() {
129 Some(_) if record.version() == version => Some(record),
130 Some(_) => None,
131 None => Some(record),
132 }
133 }
134
135 fn get_by_semver(&self, package: &PackageId, version: &SemVer) -> Option<&PackageRecord> {
137 self.available_versions(package)?.get(version)
138 }
139
140 fn get_exact_version(&self, package: &PackageId, version: &Version) -> Option<&PackageRecord> {
142 match version.digest.as_ref() {
143 Some(_) => self.get_by_version(package, version),
144 None => None,
145 }
146 }
147
148 fn get_by_digest(&self, package: &PackageId, digest: &Word) -> Option<&PackageRecord> {
150 self.available_versions(package).and_then(|versions| {
151 versions
152 .values()
153 .rev()
154 .find(|record| record.version().digest.as_ref() == Some(digest))
155 })
156 }
157
158 fn find_latest<'a>(
160 &'a self,
161 package: &PackageId,
162 requirement: &VersionRequirement,
163 ) -> Option<&'a PackageRecord> {
164 if let VersionRequirement::Exact(version) = requirement {
165 return self.get_exact_version(package, version);
166 }
167
168 self.available_versions(package).and_then(|versions| {
169 versions.values().rev().find(|record| record.version().satisfies(requirement))
170 })
171 }
172}
173
174pub trait PackageProvider {
176 fn load_package(
178 &self,
179 package: &PackageId,
180 version: &Version,
181 ) -> Result<Arc<MastPackage>, Report>;
182}
183
184pub trait PackageRegistryAndProvider: PackageRegistry + PackageProvider {}
190
191impl<T: ?Sized + PackageProvider + PackageRegistry> PackageRegistryAndProvider for T {}
192
193pub trait PackageIndex: PackageRegistry {
195 type Error: fmt::Display;
196
197 fn register(&mut self, name: PackageId, record: PackageRecord) -> Result<(), Self::Error>;
202}
203
204pub trait PackageCache: PackageRegistryAndProvider {
206 type Error: fmt::Display;
207
208 fn cache_package(&mut self, package: Arc<MastPackage>) -> Result<Version, Self::Error>;
210}
211
212pub trait PackageStore: PackageCache {
214 fn publish_package(&mut self, package: Arc<MastPackage>) -> Result<Version, Self::Error>;
216}
217
218#[derive(Debug, thiserror::Error)]
220#[error("{0}")]
221pub struct NoPackageStoreError(String);
222
223#[derive(Default)]
228pub struct NoPackageStore;
229
230impl PackageRegistry for NoPackageStore {
231 fn available_versions(&self, _package: &PackageId) -> Option<&PackageVersions> {
232 None
233 }
234}
235
236impl PackageProvider for NoPackageStore {
237 fn load_package(
238 &self,
239 package: &PackageId,
240 version: &Version,
241 ) -> Result<Arc<MastPackage>, Report> {
242 Err(Report::msg(format!("cannot load package {package}@{version}")))
243 }
244}
245
246impl PackageCache for NoPackageStore {
247 type Error = NoPackageStoreError;
248
249 fn cache_package(&mut self, package: Arc<MastPackage>) -> Result<Version, Self::Error> {
250 Ok(Version::new(package.version.clone(), package.dependency_commitment()))
251 }
252}
253
254impl PackageStore for NoPackageStore {
255 fn publish_package(&mut self, package: Arc<MastPackage>) -> Result<Version, Self::Error> {
256 Err(NoPackageStoreError(format!(
257 "cannot publish package {}@{}",
258 package.name, package.version
259 )))
260 }
261}
262
263#[cfg(test)]
264mod tests {
265 use alloc::{vec, vec::Vec};
266
267 use miden_assembly_syntax::ast::{Path as AstPath, PathBuf};
268 use miden_core::{
269 mast::{
270 BasicBlockNodeBuilder, DenseMastForestBuilder, MastForest, MastNodeExt, MastNodeId,
271 },
272 operations::Operation,
273 };
274 use miden_mast_package::{Package, PackageExport, ProcedureExport, TargetType};
275
276 use super::*;
277
278 fn build_forest() -> (MastForest, MastNodeId) {
279 let mut builder = DenseMastForestBuilder::new();
280 let node_id = builder
281 .push_node(BasicBlockNodeBuilder::new(vec![Operation::Add]))
282 .expect("failed to build basic block");
283 builder.mark_root(node_id);
284 let (forest, remapping) = builder.build_with_id_map().expect("failed to build forest");
285 let node_id = remapping.get(node_id).expect("root node should be retained");
286 (forest, node_id)
287 }
288
289 fn absolute_path(name: &str) -> Arc<AstPath> {
290 let path = PathBuf::new(name).expect("invalid path");
291 let path = path.as_path().to_absolute().unwrap().into_owned();
292 Arc::from(path.into_boxed_path())
293 }
294
295 fn build_package_exports(export: &str) -> (Arc<MastForest>, Vec<PackageExport>) {
296 let (forest, node_id) = build_forest();
297 let path = absolute_path(export);
298 let export =
299 ProcedureExport::new(Arc::clone(&path), Some(node_id), forest[node_id].digest(), None);
300
301 (Arc::new(forest), vec![PackageExport::Procedure(export)])
302 }
303
304 #[test]
305 fn no_package_store_cache_package_is_noop() {
306 let (mast, exports) = build_package_exports("test::pkg::entry");
307 let package = Arc::new(
308 Package::create(
309 PackageId::from("pkg"),
310 "1.0.0".parse().unwrap(),
311 TargetType::Library,
312 mast,
313 exports,
314 [],
315 )
316 .expect("test package should be valid"),
317 );
318 let expected = Version::new(package.version.clone(), package.dependency_commitment());
319
320 let mut store = NoPackageStore;
321 let cached = store
322 .cache_package(Arc::clone(&package))
323 .expect("no package store should accept cache writes as no-op");
324
325 assert_eq!(cached, expected);
326 assert!(store.available_versions(&package.name).is_none());
327 store
328 .load_package(&package.name, &cached)
329 .expect_err("no package store should not persist cache writes");
330 store
331 .publish_package(package)
332 .expect_err("no package store should still reject publication");
333 }
334
335 #[test]
336 fn package_registry_is_available_requires_at_least_one_version() {
337 struct EmptyVersionRegistry {
338 versions: PackageVersions,
339 }
340
341 impl PackageRegistry for EmptyVersionRegistry {
342 fn available_versions(&self, _package: &PackageId) -> Option<&PackageVersions> {
343 Some(&self.versions)
344 }
345 }
346
347 let registry = EmptyVersionRegistry { versions: BTreeMap::new() };
348
349 assert!(!registry.is_available(&PackageId::from("pkg")));
350 }
351}