1mod id;
2mod manifest;
3mod section;
4#[cfg(test)]
5mod seed_gen;
6mod serialization;
7mod target_type;
8
9use alloc::{
10 boxed::Box,
11 format,
12 string::{String, ToString},
13 sync::Arc,
14 vec::Vec,
15};
16
17use miden_assembly_syntax::{
18 KernelLibrary, Library, Report, ast::QualifiedProcedureName, library::ModuleInfo,
19};
20use miden_core::{Word, program::Kernel, serde::Deserializable};
21#[cfg(feature = "serde")]
22use serde::{Deserialize, Serialize};
23
24pub use self::{
25 id::PackageId,
26 manifest::{
27 ConstantExport, ManifestValidationError, PackageExport, PackageManifest, ProcedureExport,
28 TypeExport,
29 },
30 section::{InvalidSectionIdError, Section, SectionId},
31 target_type::{InvalidTargetTypeError, TargetType},
32};
33use crate::{Dependency, Version};
34
35#[derive(Debug, Clone, Eq, PartialEq)]
47#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
48pub struct Package {
49 pub name: PackageId,
51 pub version: Version,
53 #[cfg_attr(feature = "serde", serde(default))]
55 pub description: Option<String>,
56 pub kind: TargetType,
58 pub mast: Arc<Library>,
65 pub manifest: PackageManifest,
68 #[cfg_attr(feature = "serde", serde(default))]
71 pub sections: Vec<Section>,
72}
73
74impl Package {
76 pub fn from_library(
78 name: PackageId,
79 version: Version,
80 kind: TargetType,
81 library: Arc<Library>,
82 dependencies: impl IntoIterator<Item = Dependency>,
83 ) -> Box<Package> {
84 let manifest = PackageManifest::from_library(&library)
85 .with_dependencies(dependencies)
86 .expect("package dependencies should be unique");
87
88 Box::new(Self {
89 name,
90 version,
91 description: None,
92 kind,
93 mast: library,
94 manifest,
95 sections: Vec::new(),
96 })
97 }
98}
99
100impl Package {
102 pub const EXTENSION: &str = "masp";
104
105 pub fn digest(&self) -> Word {
107 *self.mast.digest()
108 }
109
110 pub fn is_program(&self) -> bool {
112 self.kind.is_executable()
113 }
114
115 pub fn is_library(&self) -> bool {
117 self.kind.is_library()
118 }
119
120 pub fn is_kernel(&self) -> bool {
122 matches!(self.kind, TargetType::Kernel)
123 }
124
125 pub fn kernel_module_info(&self) -> Result<ModuleInfo, Report> {
127 self.mast
128 .module_infos()
129 .find(|mi| mi.path().is_kernel_path())
130 .ok_or_else(|| Report::msg("invalid kernel package: does not contain kernel module"))
131 }
132
133 pub fn to_kernel(&self) -> Result<Kernel, Report> {
135 let exports = self
136 .manifest
137 .exports()
138 .filter_map(|export| {
139 if export.namespace().is_kernel_path()
140 && let PackageExport::Procedure(p) = export
141 {
142 Some(p.digest)
143 } else {
144 None
145 }
146 })
147 .collect::<Vec<_>>();
148 Kernel::new(&exports).map_err(|err| Report::msg(format!("invalid kernel package: {err}")))
149 }
150
151 pub fn try_into_kernel_library(&self) -> Result<KernelLibrary, Report> {
155 if !self.is_kernel() {
156 return Err(Report::msg(format!(
157 "expected package '{}' to contain a kernel, but kind was '{}'",
158 self.name, self.kind
159 )));
160 }
161
162 KernelLibrary::try_from(self.mast.clone()).map_err(|error| Report::msg(error.to_string()))
163 }
164
165 #[doc(hidden)]
167 pub fn try_into_program(&self) -> Result<miden_core::program::Program, Report> {
168 use miden_assembly_syntax::{Path as MasmPath, ast};
169 use miden_core::program::Program;
170
171 if !self.is_program() {
172 return Err(Report::msg(format!(
173 "cannot convert package of type {} to Executable",
174 self.kind
175 )));
176 }
177 let main_path = MasmPath::exec_path().join(ast::ProcedureName::MAIN_PROC_NAME);
178 if let Some(digest) = self.mast.get_procedure_root_by_path(&main_path)
179 && let Some(entrypoint) = self.mast.mast_forest().find_procedure_root(digest)
180 {
181 let mast_forest = self.mast.mast_forest().clone();
182 match self.try_embedded_kernel_library()? {
183 Some(kernel_library) => Ok(Program::with_kernel(
184 mast_forest,
185 entrypoint,
186 kernel_library.kernel().clone(),
187 )),
188 None => Ok(Program::new(mast_forest, entrypoint)),
189 }
190 } else {
191 Err(Report::msg(format!(
192 "malformed executable package: no procedure root for '{main_path}'"
193 )))
194 }
195 }
196
197 #[doc(hidden)]
199 pub fn unwrap_program(&self) -> miden_core::program::Program {
200 assert_eq!(self.kind, TargetType::Executable);
201 self.try_into_program().unwrap_or_else(|err| panic!("{err}"))
202 }
203
204 #[doc(hidden)]
205 pub fn try_embedded_kernel_package(&self) -> Result<Option<Self>, Report> {
206 let Some(kernel_package) = self.embedded_kernel_package()? else {
207 return Ok(None);
208 };
209 self.validate_embedded_kernel_dependency(&kernel_package)?;
210 Ok(Some(kernel_package))
211 }
212
213 fn try_embedded_kernel_library(&self) -> Result<Option<KernelLibrary>, Report> {
214 let Some(kernel_package) = self.try_embedded_kernel_package()? else {
215 return Ok(None);
216 };
217 kernel_package.try_into_kernel_library().map(Some)
218 }
219
220 fn embedded_kernel_package(&self) -> Result<Option<Self>, Report> {
228 let mut sections = self.sections.iter().filter(|section| section.id == SectionId::KERNEL);
229 let Some(section) = sections.next() else {
230 return Ok(None);
231 };
232 if sections.next().is_some() {
233 return Err(Report::msg(format!(
234 "package '{}' contains multiple '{}' sections",
235 self.name,
236 SectionId::KERNEL
237 )));
238 }
239
240 Self::read_from_bytes(section.data.as_ref()).map(Some).map_err(|error| {
241 Report::msg(format!(
242 "failed to decode embedded kernel package for '{}': {error}",
243 self.name
244 ))
245 })
246 }
247
248 fn validate_embedded_kernel_dependency(&self, kernel_package: &Self) -> Result<(), Report> {
249 if !kernel_package.is_kernel() {
250 return Err(Report::msg(format!(
251 "package '{}' embeds '{}', but its kind is '{}'",
252 self.name, kernel_package.name, kernel_package.kind
253 )));
254 }
255
256 let Some(kernel_dependency) = self.kernel_runtime_dependency()? else {
257 return Err(Report::msg(format!(
258 "package '{}' embeds a kernel package, but does not declare a kernel runtime dependency",
259 self.name
260 )));
261 };
262
263 if kernel_dependency.name != kernel_package.name
264 || kernel_dependency.version != kernel_package.version
265 || kernel_dependency.digest != kernel_package.digest()
266 {
267 return Err(Report::msg(format!(
268 "package '{}' declares kernel runtime dependency '{}@{}#{}', but that does not match the embedded kernel package '{}@{}#{}'",
269 self.name,
270 kernel_dependency.name,
271 kernel_dependency.version,
272 kernel_dependency.digest,
273 kernel_package.name,
274 kernel_package.version,
275 kernel_package.digest()
276 )));
277 }
278
279 Ok(())
280 }
281
282 pub fn to_dependency(&self) -> Dependency {
283 Dependency {
284 name: self.name.clone(),
285 version: self.version.clone(),
286 kind: self.kind,
287 digest: self.digest(),
288 }
289 }
290
291 pub fn kernel_runtime_dependency(&self) -> Result<Option<&Dependency>, Report> {
296 let mut kernel_dependencies = self
297 .manifest
298 .dependencies()
299 .filter(|dependency| dependency.kind == TargetType::Kernel);
300 let Some(kernel_dependency) = kernel_dependencies.next() else {
301 return Ok(None);
302 };
303 if kernel_dependencies.next().is_some() {
304 return Err(Report::msg(format!(
305 "package '{}' declares multiple kernel runtime dependencies",
306 self.name
307 )));
308 }
309
310 Ok(Some(kernel_dependency))
311 }
312
313 pub fn make_executable(&self, entrypoint: &QualifiedProcedureName) -> Result<Self, Report> {
326 use miden_assembly_syntax::{
327 Path as MasmPath, ast as masm,
328 library::{self, LibraryExport},
329 };
330 if !self.is_library() {
331 return Err(Report::msg("expected library but got an executable"));
332 }
333
334 let module = self
335 .mast
336 .module_infos()
337 .find(|info| info.path() == entrypoint.namespace())
338 .ok_or_else(|| {
339 Report::msg(format!(
340 "invalid entrypoint: library does not contain a module named '{}'",
341 entrypoint.namespace()
342 ))
343 })?;
344 if let Some(digest) = module.get_procedure_digest_by_name(entrypoint.name()) {
345 let mast_forest = self.mast.mast_forest().clone();
346 let node_id = mast_forest.find_procedure_root(digest).ok_or_else(|| {
347 Report::msg(
348 "invalid entrypoint: malformed library - procedure exported, but digest has \
349 no node in the forest",
350 )
351 })?;
352
353 let exec_path: Arc<MasmPath> =
354 MasmPath::exec_path().join(masm::ProcedureName::MAIN_PROC_NAME).into();
355 Ok(Self {
356 name: self.name.clone(),
357 version: self.version.clone(),
358 description: self.description.clone(),
359 kind: TargetType::Executable,
360 mast: Arc::new(Library::new(
361 mast_forest,
362 alloc::collections::BTreeMap::from_iter([(
363 exec_path.clone(),
364 LibraryExport::Procedure(library::ProcedureExport {
365 node: node_id,
366 path: exec_path,
367 signature: None,
368 attributes: Default::default(),
369 }),
370 )]),
371 )?),
372 manifest: PackageManifest::new(
373 self.manifest
374 .get_procedures_by_digest(&digest)
375 .cloned()
376 .map(PackageExport::Procedure),
377 )
378 .and_then(|manifest| {
379 manifest.with_dependencies(self.manifest.dependencies().cloned())
380 })
381 .expect("executable package manifest should remain valid"),
382 sections: self.sections.clone(),
383 })
384 } else {
385 Err(Report::msg(format!(
386 "invalid entrypoint: library does not export '{entrypoint}'"
387 )))
388 }
389 }
390
391 pub fn procedure_name(&self, digest: &Word) -> Option<&str> {
395 self.mast.mast_forest().procedure_name(digest)
396 }
397
398 pub fn procedure_names(&self) -> impl Iterator<Item = (Word, &Arc<str>)> {
400 self.mast.mast_forest().procedure_names()
401 }
402
403 #[cfg(feature = "std")]
405 pub fn write_to_file(&self, path: impl AsRef<std::path::Path>) -> std::io::Result<()> {
406 use miden_core::serde::Serializable;
407
408 let path = path.as_ref();
409 if let Some(dir) = path.parent() {
410 std::fs::create_dir_all(dir)?;
411 }
412
413 let mut file = std::fs::File::create(path)?;
414 <Self as Serializable>::write_into(self, &mut file);
415 Ok(())
416 }
417
418 #[cfg(feature = "std")]
420 pub fn write_masp_file(&self, dir: impl AsRef<std::path::Path>) -> std::io::Result<()> {
421 let dir = dir.as_ref();
422 let package_name: &str = &self.name;
423 self.write_to_file(dir.join(package_name).with_extension(Self::EXTENSION))
424 .map_err(|err| std::io::Error::other(err.to_string()))
425 }
426}
427
428#[cfg(feature = "arbitrary")]
429impl Package {
430 pub fn generate(
431 name: PackageId,
432 version: Version,
433 kind: TargetType,
434 dependencies: impl IntoIterator<Item = Dependency>,
435 ) -> Box<Self> {
436 let library = arbitrary_library();
437
438 Self::from_library(name, version, kind, library, dependencies)
439 }
440}
441
442#[cfg(feature = "arbitrary")]
443fn arbitrary_library() -> Arc<Library> {
444 use proptest::prelude::*;
445
446 let mut runner = proptest::test_runner::TestRunner::deterministic();
447 let value_tree = <Library as Arbitrary>::arbitrary().new_tree(&mut runner).unwrap();
448 Arc::new(value_tree.current())
449}
450
451#[cfg(test)]
455mod tests {
456 use alloc::{collections::BTreeMap, sync::Arc, vec, vec::Vec};
457
458 use miden_assembly_syntax::{
459 Library,
460 ast::{Path as AstPath, PathBuf},
461 library::{LibraryExport, ProcedureExport as LibraryProcedureExport},
462 };
463 use miden_core::{
464 mast::{BasicBlockNodeBuilder, MastForest, MastForestContributor, MastNodeId},
465 operations::Operation,
466 serde::Serializable,
467 };
468
469 use super::*;
470 use crate::{Dependency, Version};
471
472 fn build_forest() -> (MastForest, MastNodeId) {
473 let mut forest = MastForest::new();
474 let node_id = BasicBlockNodeBuilder::new(vec![Operation::Add], Vec::new())
475 .add_to_forest(&mut forest)
476 .expect("failed to build basic block");
477 forest.make_root(node_id);
478 (forest, node_id)
479 }
480
481 fn absolute_path(name: &str) -> Arc<AstPath> {
482 let path = PathBuf::new(name).expect("invalid path");
483 let path = path.as_path().to_absolute().into_owned();
484 Arc::from(path.into_boxed_path())
485 }
486
487 fn build_library(export: &str) -> Arc<Library> {
488 let (forest, node_id) = build_forest();
489 let path = absolute_path(export);
490 let export = LibraryProcedureExport::new(node_id, Arc::clone(&path));
491
492 let mut exports = BTreeMap::new();
493 exports.insert(path, LibraryExport::Procedure(export));
494
495 Arc::new(Library::new(Arc::new(forest), exports).expect("failed to build library"))
496 }
497
498 fn build_package(
499 name: &str,
500 kind: TargetType,
501 export: &str,
502 dependencies: impl IntoIterator<Item = Dependency>,
503 sections: Vec<Section>,
504 ) -> Package {
505 let mut package = *Package::from_library(
506 PackageId::from(name),
507 Version::new(1, 0, 0),
508 kind,
509 build_library(export),
510 dependencies,
511 );
512 package.sections = sections;
513 package
514 }
515
516 fn build_kernel_package(name: &str) -> Package {
517 build_package(name, TargetType::Kernel, &format!("{name}::boot"), [], Vec::new())
518 }
519
520 fn kernel_dependency(package: &Package) -> Dependency {
521 Dependency {
522 name: package.name.clone(),
523 kind: TargetType::Kernel,
524 version: package.version.clone(),
525 digest: package.digest(),
526 }
527 }
528
529 #[test]
530 fn embedded_kernel_package_rejects_duplicate_kernel_sections() {
531 let kernel = build_kernel_package("kernel");
532 let kernel_bytes = kernel.to_bytes();
533 let package = build_package(
534 "app",
535 TargetType::Library,
536 "app::entry",
537 vec![kernel_dependency(&kernel)],
538 vec![
539 Section::new(SectionId::KERNEL, kernel_bytes.clone()),
540 Section::new(SectionId::KERNEL, kernel_bytes),
541 ],
542 );
543
544 let error = package
545 .try_embedded_kernel_package()
546 .expect_err("duplicate kernel sections should be rejected");
547
548 assert!(error.to_string().contains("multiple 'kernel' sections"));
549 }
550
551 #[test]
552 fn embedded_kernel_package_rejects_multiple_kernel_runtime_dependencies() {
553 let kernel_a = build_kernel_package("kernel-a");
554 let kernel_b = build_kernel_package("kernel-b");
555 let package = build_package(
556 "app",
557 TargetType::Library,
558 "app::entry",
559 vec![kernel_dependency(&kernel_a), kernel_dependency(&kernel_b)],
560 vec![Section::new(SectionId::KERNEL, kernel_a.to_bytes())],
561 );
562
563 let error = package
564 .try_embedded_kernel_package()
565 .expect_err("multiple kernel runtime dependencies should be rejected");
566
567 assert!(error.to_string().contains("declares multiple kernel runtime dependencies"));
568 }
569}