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 = "arbitrary")]
27use proptest::prelude::*;
28#[cfg(feature = "serde")]
29use serde::{Deserialize, Serialize};
30
31#[cfg(feature = "resolver")]
32pub use self::resolver::{
33 DependencyResolutionError, InMemoryPackageRegistry, PackagePriority, PackageResolver,
34 VersionSet,
35};
36pub use self::{
37 version::{InvalidVersionError, SemVerError, Version},
38 version_requirement::VersionRequirement,
39};
40
41pub type PackageRequirements = BTreeMap<PackageId, VersionRequirement>;
43
44#[derive(Debug, Clone, PartialEq, Eq)]
46#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
47#[cfg_attr(all(feature = "arbitrary", test), miden_test_serde_macros::serde_test)]
48pub struct PackageRecord {
49 version: Version,
51 description: Option<Arc<str>>,
53 dependencies: PackageRequirements,
55}
56
57impl PackageRecord {
58 pub fn new(
60 version: Version,
61 dependencies: impl IntoIterator<Item = (PackageId, VersionRequirement)>,
62 ) -> Self {
63 Self {
64 version,
65 description: None,
66 dependencies: dependencies.into_iter().collect(),
67 }
68 }
69
70 pub fn with_description(mut self, description: impl Into<Arc<str>>) -> Self {
72 self.description = Some(description.into());
73 self
74 }
75
76 pub fn version(&self) -> &Version {
78 &self.version
79 }
80
81 pub fn semantic_version(&self) -> &SemVer {
83 &self.version.version
84 }
85
86 pub fn digest(&self) -> Option<&Word> {
88 self.version.digest.as_ref()
89 }
90
91 pub fn description(&self) -> Option<&Arc<str>> {
93 self.description.as_ref()
94 }
95
96 pub fn dependencies(&self) -> &PackageRequirements {
98 &self.dependencies
99 }
100}
101
102#[cfg(feature = "arbitrary")]
103impl Arbitrary for PackageRecord {
104 type Parameters = ();
105 type Strategy = BoxedStrategy<Self>;
106
107 fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
108 let description = proptest::option::of(
109 proptest::collection::vec(proptest::char::range('a', 'z'), 1..32)
110 .prop_map(|chars| Arc::<str>::from(chars.into_iter().collect::<String>())),
111 );
112 let dependencies =
113 proptest::collection::vec((any::<PackageId>(), any::<VersionRequirement>()), 0..4)
114 .prop_map(|entries| entries.into_iter().collect::<BTreeMap<_, _>>());
115
116 (any::<Version>(), description, dependencies)
117 .prop_map(|(version, description, dependencies)| {
118 let mut record = Self::new(version, dependencies);
119 if let Some(description) = description {
120 record = record.with_description(description);
121 }
122 record
123 })
124 .boxed()
125 }
126}
127
128pub type PackageVersions = BTreeMap<SemVer, PackageRecord>;
133
134pub trait PackageRegistry {
136 fn available_versions(&self, package: &PackageId) -> Option<&PackageVersions>;
138
139 fn is_available(&self, package: &PackageId) -> bool {
141 self.available_versions(package).is_some_and(|versions| !versions.is_empty())
142 }
143
144 fn is_version_available(&self, package: &PackageId, version: &Version) -> bool {
146 self.get_by_version(package, version).is_some()
147 }
148
149 fn is_semver_available(&self, package: &PackageId, version: &SemVer) -> bool {
151 self.get_by_semver(package, version).is_some()
152 }
153
154 fn get_by_version(&self, package: &PackageId, version: &Version) -> Option<&PackageRecord> {
156 let record = self.available_versions(package)?.get(&version.version)?;
157 match version.digest.as_ref() {
158 Some(_) if record.version() == version => Some(record),
159 Some(_) => None,
160 None => Some(record),
161 }
162 }
163
164 fn get_by_semver(&self, package: &PackageId, version: &SemVer) -> Option<&PackageRecord> {
166 self.available_versions(package)?.get(version)
167 }
168
169 fn get_exact_version(&self, package: &PackageId, version: &Version) -> Option<&PackageRecord> {
171 match version.digest.as_ref() {
172 Some(_) => self.get_by_version(package, version),
173 None => None,
174 }
175 }
176
177 fn get_by_digest(&self, package: &PackageId, digest: &Word) -> Option<&PackageRecord> {
179 self.available_versions(package).and_then(|versions| {
180 versions
181 .values()
182 .rev()
183 .find(|record| record.version().digest.as_ref() == Some(digest))
184 })
185 }
186
187 fn find_latest<'a>(
189 &'a self,
190 package: &PackageId,
191 requirement: &VersionRequirement,
192 ) -> Option<&'a PackageRecord> {
193 if let VersionRequirement::Exact(version) = requirement {
194 return self.get_exact_version(package, version);
195 }
196
197 self.available_versions(package).and_then(|versions| {
198 versions.values().rev().find(|record| record.version().satisfies(requirement))
199 })
200 }
201}
202
203pub trait PackageProvider {
205 fn load_package(
207 &self,
208 package: &PackageId,
209 version: &Version,
210 ) -> Result<Arc<MastPackage>, Report>;
211}
212
213pub trait PackageRegistryAndProvider: PackageRegistry + PackageProvider {}
219
220impl<T: ?Sized + PackageProvider + PackageRegistry> PackageRegistryAndProvider for T {}
221
222pub trait PackageIndex: PackageRegistry {
224 type Error: fmt::Display;
225
226 fn register(&mut self, name: PackageId, record: PackageRecord) -> Result<(), Self::Error>;
231}
232
233pub trait PackageCache: PackageRegistryAndProvider {
235 type Error: fmt::Display;
236
237 fn cache_package(&mut self, package: Arc<MastPackage>) -> Result<Version, Self::Error>;
239}
240
241pub trait PackageStore: PackageCache {
243 fn publish_package(&mut self, package: Arc<MastPackage>) -> Result<Version, Self::Error>;
245}
246
247#[derive(Debug, thiserror::Error)]
249#[error("{0}")]
250pub struct NoPackageStoreError(String);
251
252#[derive(Default)]
257pub struct NoPackageStore;
258
259impl PackageRegistry for NoPackageStore {
260 fn available_versions(&self, _package: &PackageId) -> Option<&PackageVersions> {
261 None
262 }
263}
264
265impl PackageProvider for NoPackageStore {
266 fn load_package(
267 &self,
268 package: &PackageId,
269 version: &Version,
270 ) -> Result<Arc<MastPackage>, Report> {
271 Err(Report::msg(format!("cannot load package {package}@{version}")))
272 }
273}
274
275impl PackageCache for NoPackageStore {
276 type Error = NoPackageStoreError;
277
278 fn cache_package(&mut self, package: Arc<MastPackage>) -> Result<Version, Self::Error> {
279 Ok(Version::new(package.version.clone(), package.digest()))
280 }
281}
282
283impl PackageStore for NoPackageStore {
284 fn publish_package(&mut self, package: Arc<MastPackage>) -> Result<Version, Self::Error> {
285 Err(NoPackageStoreError(format!(
286 "cannot publish package {}@{}",
287 package.name, package.version
288 )))
289 }
290}
291
292#[cfg(test)]
293mod tests {
294 use alloc::{vec, vec::Vec};
295
296 use miden_assembly_syntax::ast::{Path as AstPath, PathBuf};
297 use miden_core::{
298 mast::{
299 BasicBlockNodeBuilder, DenseMastForestBuilder, MastForest, MastNodeExt, MastNodeId,
300 },
301 operations::Operation,
302 };
303 use miden_mast_package::{Package, PackageExport, ProcedureExport, TargetType};
304
305 use super::*;
306
307 fn build_forest() -> (MastForest, MastNodeId) {
308 let mut builder = DenseMastForestBuilder::new();
309 let node_id = builder
310 .push_node(BasicBlockNodeBuilder::new(vec![Operation::Add]))
311 .expect("failed to build basic block");
312 builder.mark_root(node_id);
313 let (forest, remapping) = builder.build_with_id_map().expect("failed to build forest");
314 let node_id = remapping.get(node_id).expect("root node should be retained");
315 (forest, node_id)
316 }
317
318 fn absolute_path(name: &str) -> Arc<AstPath> {
319 let path = PathBuf::new(name).expect("invalid path");
320 let path = path.as_path().to_absolute().unwrap().into_owned();
321 Arc::from(path.into_boxed_path())
322 }
323
324 fn build_package_exports(export: &str) -> (Arc<MastForest>, Vec<PackageExport>) {
325 let (forest, node_id) = build_forest();
326 let path = absolute_path(export);
327 let export =
328 ProcedureExport::new(Arc::clone(&path), Some(node_id), forest[node_id].digest(), None);
329
330 (Arc::new(forest), vec![PackageExport::Procedure(export)])
331 }
332
333 #[test]
334 fn no_package_store_cache_package_is_noop() {
335 let (mast, exports) = build_package_exports("test::pkg::entry");
336 let package = Arc::new(
337 Package::create(
338 PackageId::from("pkg"),
339 "1.0.0".parse().unwrap(),
340 TargetType::Library,
341 mast,
342 exports,
343 [],
344 )
345 .expect("test package should be valid"),
346 );
347 let expected = Version::new(package.version.clone(), package.digest());
348
349 let mut store = NoPackageStore;
350 let cached = store
351 .cache_package(Arc::clone(&package))
352 .expect("no package store should accept cache writes as no-op");
353
354 assert_eq!(cached, expected);
355 assert!(store.available_versions(&package.name).is_none());
356 store
357 .load_package(&package.name, &cached)
358 .expect_err("no package store should not persist cache writes");
359 store
360 .publish_package(package)
361 .expect_err("no package store should still reject publication");
362 }
363
364 #[test]
365 fn package_registry_is_available_requires_at_least_one_version() {
366 struct EmptyVersionRegistry {
367 versions: PackageVersions,
368 }
369
370 impl PackageRegistry for EmptyVersionRegistry {
371 fn available_versions(&self, _package: &PackageId) -> Option<&PackageVersions> {
372 Some(&self.versions)
373 }
374 }
375
376 let registry = EmptyVersionRegistry { versions: BTreeMap::new() };
377
378 assert!(!registry.is_available(&PackageId::from("pkg")));
379 }
380}