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()
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 PackageIndex: PackageRegistry {
215 type Error: fmt::Display;
216
217 fn register(&mut self, name: PackageId, record: PackageRecord) -> Result<(), Self::Error>;
222}
223
224pub trait PackageCache: PackageRegistry + PackageProvider {
226 type Error: fmt::Display;
227
228 fn cache_package(&mut self, package: Arc<MastPackage>) -> Result<Version, Self::Error>;
230}
231
232pub trait PackageStore: PackageCache {
234 fn publish_package(&mut self, package: Arc<MastPackage>) -> Result<Version, Self::Error>;
236}
237
238#[derive(Debug, thiserror::Error)]
240#[error("{0}")]
241pub struct NoPackageStoreError(String);
242
243#[derive(Default)]
248pub struct NoPackageStore;
249
250impl PackageRegistry for NoPackageStore {
251 fn available_versions(&self, _package: &PackageId) -> Option<&PackageVersions> {
252 None
253 }
254}
255
256impl PackageProvider for NoPackageStore {
257 fn load_package(
258 &self,
259 package: &PackageId,
260 version: &Version,
261 ) -> Result<Arc<MastPackage>, Report> {
262 Err(Report::msg(format!("cannot load package {package}@{version}")))
263 }
264}
265
266impl PackageCache for NoPackageStore {
267 type Error = NoPackageStoreError;
268
269 fn cache_package(&mut self, package: Arc<MastPackage>) -> Result<Version, Self::Error> {
270 Ok(Version::new(package.version.clone(), package.digest()))
271 }
272}
273
274impl PackageStore for NoPackageStore {
275 fn publish_package(&mut self, package: Arc<MastPackage>) -> Result<Version, Self::Error> {
276 Err(NoPackageStoreError(format!(
277 "cannot publish package {}@{}",
278 package.name, package.version
279 )))
280 }
281}
282
283#[cfg(test)]
284mod tests {
285 use alloc::{collections::BTreeMap, vec, vec::Vec};
286
287 use miden_assembly_syntax::{
288 Library,
289 ast::{Path as AstPath, PathBuf},
290 library::{LibraryExport, ProcedureExport as LibraryProcedureExport},
291 };
292 use miden_core::{
293 mast::{BasicBlockNodeBuilder, MastForest, MastForestContributor, MastNodeId},
294 operations::Operation,
295 };
296 use miden_mast_package::{Package, TargetType};
297
298 use super::*;
299
300 fn build_forest() -> (MastForest, MastNodeId) {
301 let mut forest = MastForest::new();
302 let node_id = BasicBlockNodeBuilder::new(vec![Operation::Add], Vec::new())
303 .add_to_forest(&mut forest)
304 .expect("failed to build basic block");
305 forest.make_root(node_id);
306 (forest, node_id)
307 }
308
309 fn absolute_path(name: &str) -> Arc<AstPath> {
310 let path = PathBuf::new(name).expect("invalid path");
311 let path = path.as_path().to_absolute().into_owned();
312 Arc::from(path.into_boxed_path())
313 }
314
315 fn build_library(export: &str) -> Arc<Library> {
316 let (forest, node_id) = build_forest();
317 let path = absolute_path(export);
318 let export = LibraryProcedureExport::new(node_id, Arc::clone(&path));
319
320 let mut exports = BTreeMap::new();
321 exports.insert(path, LibraryExport::Procedure(export));
322
323 Arc::new(Library::new(Arc::new(forest), exports).expect("failed to build library"))
324 }
325
326 #[test]
327 fn no_package_store_cache_package_is_noop() {
328 let package: Arc<MastPackage> = Package::from_library(
329 PackageId::from("pkg"),
330 "1.0.0".parse().unwrap(),
331 TargetType::Library,
332 build_library("test::pkg::entry"),
333 [],
334 )
335 .into();
336 let expected = Version::new(package.version.clone(), package.digest());
337
338 let mut store = NoPackageStore;
339 let cached = store
340 .cache_package(Arc::clone(&package))
341 .expect("no package store should accept cache writes as no-op");
342
343 assert_eq!(cached, expected);
344 assert!(store.available_versions(&package.name).is_none());
345 store
346 .load_package(&package.name, &cached)
347 .expect_err("no package store should not persist cache writes");
348 store
349 .publish_package(package)
350 .expect_err("no package store should still reject publication");
351 }
352}