1use alloc::{boxed::Box, collections::BTreeMap, format, string::ToString, sync::Arc, vec::Vec};
2use std::{
3 fs,
4 path::{Path as FsPath, PathBuf},
5};
6
7use miden_assembly_syntax::{
8 ModuleParser,
9 ast::{ModuleKind, Path as MasmPath},
10 diagnostics::Report,
11};
12use miden_core::serde::{Deserializable, Serializable};
13use miden_mast_package::{Package as MastPackage, Section, SectionId, TargetType};
14use miden_package_registry::{PackageCache, PackageId, Version as PackageVersion};
15use miden_project::{
16 Linkage, Package as ProjectPackage, PreassembledDependencyMetadata, Profile,
17 ProjectDependencyNodeProvenance, ProjectSource, ProjectSourceOrigin, Target,
18};
19
20use crate::{Assembler, assembler::debuginfo::DebugInfoSections, ast::Module};
21
22mod build_provenance;
23mod dependency_graph;
24mod package_ext;
25mod runtime_dependencies;
26mod target_selector;
27
28use build_provenance::PackageBuildProvenance;
29use dependency_graph::DependencyGraph;
30use package_ext::ProjectPackageExt;
31use runtime_dependencies::RuntimeDependencies;
32pub use target_selector::ProjectTargetSelector;
33
34#[cfg(test)]
35mod tests;
36
37impl Assembler {
41 pub fn for_project_at_path<'a, S>(
43 self,
44 manifest_path: impl AsRef<FsPath>,
45 store: &'a mut S,
46 ) -> Result<ProjectAssembler<'a, S>, Report>
47 where
48 S: PackageCache + ?Sized,
49 {
50 let manifest_path = manifest_path.as_ref();
51 let source_manager = self.source_manager();
52 let project = miden_project::Project::load(manifest_path, &source_manager)?;
53 let package = project.package();
54 let dependency_graph =
55 DependencyGraph::from_project_path(manifest_path, store, source_manager)?;
56
57 Ok(ProjectAssembler {
58 assembler: self,
59 project: package,
60 dependency_graph,
61 store,
62 })
63 }
64
65 pub fn for_project<'a, S>(
67 self,
68 project: Arc<ProjectPackage>,
69 store: &'a mut S,
70 ) -> Result<ProjectAssembler<'a, S>, Report>
71 where
72 S: PackageCache + ?Sized,
73 {
74 let source_manager = self.source_manager();
75 let dependency_graph =
76 DependencyGraph::from_project(project.clone(), store, source_manager)?;
77 Ok(ProjectAssembler {
78 assembler: self,
79 project,
80 dependency_graph,
81 store,
82 })
83 }
84}
85
86pub struct ProjectSourceInputs {
90 pub root: Box<Module>,
91 pub support: Vec<Box<Module>>,
92}
93
94pub struct ProjectAssembler<'a, S: PackageCache + ?Sized> {
95 assembler: Assembler,
96 project: Arc<ProjectPackage>,
97 dependency_graph: DependencyGraph,
98 store: &'a mut S,
99}
100
101impl<'a, S> ProjectAssembler<'a, S>
102where
103 S: PackageCache + ?Sized,
104{
105 pub fn project(&self) -> &ProjectPackage {
106 self.project.as_ref()
107 }
108
109 pub fn assemble(
110 &mut self,
111 target: ProjectTargetSelector<'_>,
112 profile: &str,
113 ) -> Result<Arc<MastPackage>, Report> {
114 self.assemble_impl(target, profile, None)
115 }
116
117 pub fn assemble_with_sources(
118 &mut self,
119 target: ProjectTargetSelector<'_>,
120 profile: &str,
121 sources: ProjectSourceInputs,
122 ) -> Result<Arc<MastPackage>, Report> {
123 self.assemble_impl(target, profile, Some(sources))
124 }
125
126 fn assemble_impl(
127 &mut self,
128 target_selector: ProjectTargetSelector<'_>,
129 profile_name: &str,
130 sources: Option<ProjectSourceInputs>,
131 ) -> Result<Arc<MastPackage>, Report> {
132 let target = target_selector.select_target(self.project.as_ref())?;
133
134 let mut cache = BTreeMap::new();
137 let root_id = self.dependency_graph.root().clone();
138 let required_lib = if target.is_executable()
139 && let Some(library_target) =
140 self.project.library_target().map(|target| target.inner().clone())
141 {
142 Some(self.assemble_source_package(
143 root_id.clone(),
144 Arc::clone(&self.project),
145 &library_target,
146 profile_name,
147 None,
148 None,
149 &mut cache,
150 )?)
151 } else {
152 None
153 };
154
155 self.assemble_source_package(
156 root_id,
157 Arc::clone(&self.project),
158 &target,
159 profile_name,
160 required_lib,
161 sources,
162 &mut cache,
163 )
164 .map(|resolved| resolved.package)
165 }
166
167 fn assemble_source_package(
168 &mut self,
169 package_id: PackageId,
170 project: Arc<ProjectPackage>,
171 target: &Target,
172 profile_name: &str,
173 required_lib: Option<ResolvedPackage>,
174 sources: Option<ProjectSourceInputs>,
175 cache: &mut BTreeMap<PackageId, ResolvedPackage>,
176 ) -> Result<ResolvedPackage, Report> {
177 let cache_key = project.target_package_name(target);
178 if sources.is_none()
179 && let Some(package) = cache.get(&cache_key).cloned()
180 {
181 assert_eq!(package.package.kind, target.ty);
182 return Ok(package);
183 }
184
185 let profile = project.resolve_profile(profile_name)?;
186 let mut assembler = self
187 .assembler
188 .clone()
189 .with_emit_debug_info(profile.should_emit_debug_info())
190 .with_trim_paths(profile.should_trim_paths());
191 let mut runtime_dependencies = RuntimeDependencies::default();
192 match required_lib {
193 Some(required_lib) if required_lib.package.is_kernel() => {
194 assembler.link_package(required_lib.package.clone(), Linkage::Dynamic)?;
195 runtime_dependencies.record_linked_kernel_dependency(required_lib.package)?;
196 },
197 Some(required_lib) => {
198 assembler.link_package(required_lib.package.clone(), Linkage::Static)?;
199 if let Some(kernel_package) = required_lib.linked_kernel_package {
200 runtime_dependencies.record_linked_kernel_dependency(kernel_package)?;
201 }
202 },
203 None => (),
204 }
205
206 let node = self.dependency_graph.get(&package_id)?;
207 let dependencies = node.dependencies.clone();
208 for edge in dependencies.iter() {
209 let dependency_package =
210 self.resolve_dependency_package(&edge.dependency, profile_name, cache)?;
211 if !dependency_package.package.is_library() {
212 return Err(Report::msg(format!(
213 "dependency '{}' resolved to executable package '{}', but only library-like packages can be linked",
214 edge.dependency, dependency_package.package.name
215 )));
216 }
217
218 assembler.link_package(dependency_package.package.clone(), edge.linkage)?;
219 runtime_dependencies.merge_package(dependency_package, edge.linkage)?;
220 }
221
222 let has_provided_sources = sources.is_some();
223 let LoadedTargetSources { root, support } = match sources {
224 Some(sources) => self.normalize_provided_sources(target, sources)?,
225 None => self.load_target_sources(project.as_ref(), target)?,
226 };
227
228 let product = match target.ty {
229 TargetType::Executable => assembler.assemble_executable_modules(root, support)?,
230 TargetType::Kernel => {
231 if !support.is_empty() {
232 assembler.compile_and_statically_link_all(support)?;
233 }
234 assembler.assemble_kernel_module(root)?
235 },
236 _ if target.ty.is_library() => {
237 let mut modules = Vec::with_capacity(support.len() + 1);
238 modules.push(root);
239 modules.extend(support);
240 assembler.assemble_library_modules(modules, target.ty)?
241 },
242 _ => unreachable!("non-exhaustive target type"),
243 };
244
245 let manifest = product
246 .manifest()
247 .clone()
248 .with_dependencies(runtime_dependencies.deps.into_values())
249 .expect("assembled package manifest should have unique runtime dependencies");
250 let debug_info = product.debug_info().cloned();
251
252 let mut sections = Vec::new();
254
255 if let Some(provenance) = self.dependency_graph.build_source_provenance(
257 &package_id,
258 project.as_ref(),
259 target,
260 profile_name,
261 has_provided_sources,
262 )? {
263 sections.push(provenance.to_section());
264 }
265
266 if target.ty.is_executable()
268 && let Some(kernel_package) = runtime_dependencies.kernel.clone()
269 {
270 sections.push(linked_kernel_package_section(kernel_package.as_ref()));
271 }
272
273 if let Some(DebugInfoSections {
275 debug_sources_section,
276 debug_functions_section,
277 debug_types_section,
278 }) = debug_info.as_ref()
279 {
280 sections.push(Section::new(SectionId::DEBUG_SOURCES, debug_sources_section.to_bytes()));
281 sections
282 .push(Section::new(SectionId::DEBUG_FUNCTIONS, debug_functions_section.to_bytes()));
283 sections.push(Section::new(SectionId::DEBUG_TYPES, debug_types_section.to_bytes()));
284 }
285
286 let package = Arc::new(MastPackage {
287 name: project.target_package_name(target),
288 version: project.version().into_inner().clone(),
289 description: project.description().map(|description| description.to_string()),
290 kind: product.kind(),
291 mast: product.into_artifact(),
292 manifest,
293 sections,
294 });
295
296 let resolved = ResolvedPackage {
297 package: Arc::clone(&package),
298 linked_kernel_package: runtime_dependencies.kernel,
299 };
300 if !has_provided_sources {
301 cache.insert(package_id, resolved.clone());
302 }
303
304 Ok(resolved)
305 }
306
307 fn resolve_dependency_package(
308 &mut self,
309 package_id: &PackageId,
310 profile_name: &str,
311 cache: &mut BTreeMap<PackageId, ResolvedPackage>,
312 ) -> Result<ResolvedPackage, Report> {
313 if let Some(package) = cache.get(package_id).cloned() {
314 return Ok(package);
315 }
316
317 let node = self.dependency_graph.get(package_id)?;
318 let node_version = node.version.clone();
319
320 let (package, should_cache) = match &node.provenance {
321 ProjectDependencyNodeProvenance::Source(ProjectSource::Virtual { .. }) => {
322 return Err(Report::msg(format!(
323 "package '{package_id}' is missing a manifest path",
324 )));
325 },
326 ProjectDependencyNodeProvenance::Source(ProjectSource::Real {
327 manifest_path,
328 origin,
329 library_path: Some(_),
330 workspace_root,
331 ..
332 }) => {
333 let project = ProjectPackage::load_package(
334 self.assembler.source_manager(),
335 package_id,
336 manifest_path,
337 )?;
338 let target = project
339 .library_target()
340 .map(|target| target.inner().clone())
341 .ok_or_else(|| {
342 Report::msg(format!(
343 "dependency '{package_id}' does not define a library target"
344 ))
345 })?;
346 match self.try_reuse_registered_source_package(
347 package_id,
348 &node_version,
349 &project,
350 &target,
351 profile_name,
352 origin,
353 manifest_path,
354 workspace_root.as_deref(),
355 )? {
356 RegisteredSourcePackage::Loaded(package) => (
357 ResolvedPackage {
358 linked_kernel_package: self
359 .resolve_linked_kernel_package(package.clone())?,
360 package,
361 },
362 false,
363 ),
364 reuse => {
365 let package = self.assemble_source_package(
366 package_id.clone(),
367 project,
368 &target,
369 profile_name,
370 None,
371 None,
372 cache,
373 )?;
374 match reuse {
375 RegisteredSourcePackage::Missing => (),
376 RegisteredSourcePackage::IndexedButUnreadable(expected) => {
377 let actual = PackageVersion::new(
378 package.package.version.clone(),
379 package.package.digest(),
380 );
381 if actual != expected {
382 return Err(Report::msg(format!(
383 "package '{package_id}' version '{node_version}' is already registered as '{expected}', but the canonical artifact could not be loaded and rebuilding from source produced '{actual}'; bump the semantic version or repair the package store"
384 )));
385 }
386 },
387 RegisteredSourcePackage::Loaded(_) => unreachable!(),
388 }
389 (package, true)
390 },
391 }
392 },
393 ProjectDependencyNodeProvenance::Source(_) => {
394 let package =
395 self.load_canonical_package(package_id, &node_version)?.ok_or_else(|| {
396 Report::msg(format!(
397 "dependency '{package_id}' version '{node_version}' was not found in the package registry"
398 ))
399 })?;
400 (
401 ResolvedPackage {
402 linked_kernel_package: self
403 .resolve_linked_kernel_package(package.clone())?,
404 package,
405 },
406 false,
407 )
408 },
409 ProjectDependencyNodeProvenance::Registry { selected, .. } => {
410 let package = self.store.load_package(package_id, selected)?;
411 (
412 ResolvedPackage {
413 linked_kernel_package: self
414 .resolve_linked_kernel_package(package.clone())?,
415 package,
416 },
417 false,
418 )
419 },
420 ProjectDependencyNodeProvenance::Preassembled {
421 path,
422 selected,
423 kind,
424 requirements,
425 } => {
426 let package = load_selected_preassembled_package(
427 path,
428 package_id,
429 selected,
430 *kind,
431 requirements,
432 )?;
433 let should_cache = self.should_cache_preassembled_package(package_id, selected)?;
434 (
435 ResolvedPackage {
436 linked_kernel_package: self
437 .resolve_linked_kernel_package(package.clone())?,
438 package,
439 },
440 should_cache,
441 )
442 },
443 };
444
445 if should_cache {
446 self.cache_resolved_package(&package)?;
447 }
448 cache.insert(package_id.clone(), package.clone());
449 Ok(package)
450 }
451
452 fn resolve_linked_kernel_package(
453 &self,
454 package: Arc<MastPackage>,
455 ) -> Result<Option<Arc<MastPackage>>, Report> {
456 if package.is_kernel() {
457 return Ok(Some(package));
458 }
459
460 let Some(kernel_dependency) = package.kernel_runtime_dependency()? else {
461 return Ok(None);
462 };
463
464 let version =
465 PackageVersion::new(kernel_dependency.version.clone(), kernel_dependency.digest);
466 if self.store.get_exact_version(&kernel_dependency.name, &version).is_some() {
467 match self.store.load_package(&kernel_dependency.name, &version) {
468 Ok(kernel_package) => {
469 if !kernel_package.is_kernel() {
470 return Err(Report::msg(format!(
471 "runtime kernel dependency '{}@{}#{}' resolved to non-kernel package '{}'",
472 kernel_dependency.name,
473 kernel_dependency.version,
474 kernel_dependency.digest,
475 kernel_package.name
476 )));
477 }
478 return Ok(Some(kernel_package));
479 },
480 Err(load_error) => {
481 if let Some(kernel_package) = package
482 .try_embedded_kernel_package()
483 .map(|kernel_package| kernel_package.map(Arc::new))?
484 {
485 return Ok(Some(kernel_package));
486 }
487 return Err(load_error);
488 },
489 }
490 }
491
492 package
493 .try_embedded_kernel_package()
494 .map(|kernel_package| kernel_package.map(Arc::new))
495 }
496
497 fn load_canonical_package(
498 &self,
499 package_id: &PackageId,
500 version: &miden_project::SemVer,
501 ) -> Result<Option<Arc<MastPackage>>, Report> {
502 let Some(record) = self.store.get_by_semver(package_id, version) else {
503 return Ok(None);
504 };
505 self.store.load_package(package_id, record.version()).map(Some)
506 }
507
508 fn try_reuse_registered_source_package(
509 &self,
510 package_id: &PackageId,
511 version: &miden_project::SemVer,
512 project: &ProjectPackage,
513 target: &Target,
514 profile_name: &str,
515 origin: &ProjectSourceOrigin,
516 manifest_path: &FsPath,
517 workspace_root: Option<&FsPath>,
518 ) -> Result<RegisteredSourcePackage, Report> {
519 let Some(record) = self.store.get_by_semver(package_id, version) else {
520 return Ok(RegisteredSourcePackage::Missing);
521 };
522 let package = match self.store.load_package(package_id, record.version()) {
523 Ok(package) => package,
524 Err(_) => {
525 return Ok(RegisteredSourcePackage::IndexedButUnreadable(record.version().clone()));
526 },
527 };
528
529 let expected = self.dependency_graph.expected_source_provenance(
530 package_id,
531 project,
532 target,
533 profile_name,
534 origin,
535 manifest_path,
536 workspace_root,
537 )?;
538
539 match PackageBuildProvenance::from_package(&package)? {
540 Some(actual) if actual == expected => Ok(()),
541 Some(actual) => Err(Report::msg(format!(
542 "package '{}' version '{}' is already registered with different source provenance (expected {}, found {}); bump the semantic version",
543 package_id,
544 version,
545 expected.describe(),
546 actual.describe(),
547 ))),
548 None => Err(Report::msg(format!(
549 "package '{package_id}' version '{version}' is already registered, but the canonical artifact is missing source provenance; bump the semantic version"
550 ))),
551 }?;
552
553 Ok(RegisteredSourcePackage::Loaded(package))
554 }
555
556 fn should_cache_preassembled_package(
557 &self,
558 package_id: &PackageId,
559 selected: &PackageVersion,
560 ) -> Result<bool, Report> {
561 let Some(record) = self.store.get_by_semver(package_id, &selected.version) else {
562 return Ok(true);
563 };
564 if record.version() != selected {
565 return Ok(false);
566 }
567
568 match self.store.load_package(package_id, selected) {
569 Ok(_) => Ok(false),
570 Err(_) => Ok(true),
571 }
572 }
573
574 fn cache_resolved_package(&mut self, package: &ResolvedPackage) -> Result<(), Report> {
575 self.cache_package(package.package.clone())?;
576 if let Some(kernel_package) = package.linked_kernel_package.clone()
577 && self.should_cache_linked_kernel_package(kernel_package.as_ref())?
578 {
579 self.cache_package(kernel_package)?;
580 }
581 Ok(())
582 }
583
584 fn should_cache_linked_kernel_package(&self, package: &MastPackage) -> Result<bool, Report> {
585 let version = PackageVersion::new(package.version.clone(), package.digest());
586 let Some(record) = self.store.get_by_semver(&package.name, &package.version) else {
587 return Ok(true);
588 };
589 if record.version() != &version {
590 return Ok(false);
591 }
592
593 match self.store.load_package(&package.name, &version) {
594 Ok(_) => Ok(false),
595 Err(_) => Ok(true),
596 }
597 }
598
599 fn cache_package(&mut self, package: Arc<MastPackage>) -> Result<(), Report> {
600 self.store
601 .cache_package(package)
602 .map(|_| ())
603 .map_err(|error| Report::msg(error.to_string()))
604 }
605
606 fn normalize_provided_sources(
607 &self,
608 target: &Target,
609 sources: ProjectSourceInputs,
610 ) -> Result<LoadedTargetSources, Report> {
611 let mut root = sources.root;
612 root.set_kind(target_root_module_kind(target.ty));
613 root.set_path(target.namespace.inner().as_ref());
614
615 let support = sources
616 .support
617 .into_iter()
618 .map(|mut module| {
619 module.set_kind(ModuleKind::Library);
620 Ok(module)
621 })
622 .collect::<Result<Vec<_>, Report>>()?;
623
624 Ok(LoadedTargetSources { root, support })
625 }
626
627 fn load_target_sources(
628 &self,
629 project: &ProjectPackage,
630 target: &Target,
631 ) -> Result<LoadedTargetSources, Report> {
632 let source_paths = project.resolve_target_source_paths(target)?;
633 let root = self.parse_module_file(
634 &source_paths.root,
635 target_root_module_kind(target.ty),
636 target.namespace.inner().as_ref(),
637 )?;
638 let support = source_paths
639 .support
640 .iter()
641 .map(|path| {
642 let relative = path.strip_prefix(&source_paths.root_dir).map_err(|error| {
643 Report::msg(format!(
644 "failed to derive module path for '{}': {error}",
645 path.display()
646 ))
647 })?;
648 let module_path = module_path_from_relative(target.namespace.inner(), relative)?;
649 self.parse_module_file(path, ModuleKind::Library, module_path.as_ref())
650 })
651 .collect::<Result<Vec<_>, Report>>()?;
652
653 Ok(LoadedTargetSources { root, support })
654 }
655
656 fn parse_module_file(
657 &self,
658 path: &FsPath,
659 kind: ModuleKind,
660 module_path: &MasmPath,
661 ) -> Result<Box<Module>, Report> {
662 let mut parser = ModuleParser::new(kind);
663 parser.set_warnings_as_errors(self.assembler.warnings_as_errors());
664 parser.parse_file(module_path, path, self.assembler.source_manager())
665 }
666}
667
668#[derive(Clone)]
671struct ResolvedPackage {
672 package: Arc<MastPackage>,
673 linked_kernel_package: Option<Arc<MastPackage>>,
674}
675
676enum RegisteredSourcePackage {
677 Missing,
678 Loaded(Arc<MastPackage>),
679 IndexedButUnreadable(PackageVersion),
680}
681
682struct LoadedTargetSources {
683 root: Box<Module>,
684 #[allow(clippy::vec_box)]
685 support: Vec<Box<Module>>,
686}
687
688#[derive(Debug)]
689struct TargetSourcePaths {
690 root: PathBuf,
691 root_dir: PathBuf,
692 support: Vec<PathBuf>,
693}
694
695#[derive(Debug, Clone, PartialEq, Eq)]
696struct PackageBuildSettings {
697 emit_debug_info: bool,
698 trim_paths: bool,
699}
700
701impl PackageBuildSettings {
702 fn legacy() -> Self {
703 Self { emit_debug_info: true, trim_paths: false }
704 }
705
706 fn from_profile(profile: &Profile) -> Self {
707 Self {
708 emit_debug_info: profile.should_emit_debug_info(),
709 trim_paths: profile.should_trim_paths(),
710 }
711 }
712
713 fn is_legacy(&self) -> bool {
714 *self == Self::legacy()
715 }
716}
717
718fn target_root_module_kind(ty: TargetType) -> ModuleKind {
722 match ty {
723 TargetType::Executable => ModuleKind::Executable,
724 TargetType::Kernel => ModuleKind::Kernel,
725 _ => ModuleKind::Library,
726 }
727}
728
729fn linked_kernel_package_section(package: &MastPackage) -> Section {
730 Section::new(SectionId::KERNEL, package.to_bytes())
731}
732
733fn module_path_from_relative(
734 namespace: &MasmPath,
735 relative: &FsPath,
736) -> Result<Arc<MasmPath>, Report> {
737 let mut module_path = namespace.to_path_buf();
738 let stem = relative.with_extension("");
739 let mut components = stem
740 .iter()
741 .map(|component| {
742 component.to_str().ok_or_else(|| {
743 Report::msg(format!("module path '{}' contains invalid UTF-8", relative.display()))
744 })
745 })
746 .collect::<Result<Vec<_>, Report>>()?;
747
748 if components.last().is_some_and(|component| *component == Module::ROOT) {
749 components.pop();
750 }
751
752 for component in components {
753 MasmPath::validate(component).map_err(|error| Report::msg(error.to_string()))?;
754 module_path.push(component);
755 }
756
757 Ok(module_path.into())
758}
759
760fn load_selected_preassembled_package(
761 path: &FsPath,
762 expected_name: &PackageId,
763 selected: &PackageVersion,
764 expected_kind: TargetType,
765 expected_requirements: &BTreeMap<PackageId, PreassembledDependencyMetadata>,
766) -> Result<Arc<MastPackage>, Report> {
767 let package = load_package_from_path(path)?;
768 if &package.name != expected_name {
769 return Err(Report::msg(format!(
770 "preassembled dependency '{}' at '{}' resolved to package '{}'",
771 expected_name,
772 path.display(),
773 package.name
774 )));
775 }
776
777 let actual = PackageVersion::new(package.version.clone(), package.digest());
778 if &actual != selected {
779 return Err(Report::msg(format!(
780 "preassembled dependency '{}@{}' at '{}' no longer matches the dependency graph selection '{}'",
781 expected_name,
782 actual,
783 path.display(),
784 selected
785 )));
786 }
787
788 if package.kind != expected_kind {
789 return Err(Report::msg(format!(
790 "preassembled dependency '{}@{}' at '{}' no longer matches the dependency graph target kind '{}'",
791 expected_name,
792 actual,
793 path.display(),
794 expected_kind
795 )));
796 }
797
798 let actual_requirements = package_requirements(&package);
799 if &actual_requirements != expected_requirements {
800 return Err(Report::msg(format!(
801 "preassembled dependency '{}@{}' at '{}' no longer matches the dependency graph dependency requirements",
802 expected_name,
803 actual,
804 path.display()
805 )));
806 }
807
808 Ok(package)
809}
810
811fn load_package_from_path(path: &FsPath) -> Result<Arc<MastPackage>, Report> {
812 let bytes = fs::read(path)
813 .map_err(|error| Report::msg(format!("failed to read '{}': {error}", path.display())))?;
814 let package = MastPackage::read_from_bytes(&bytes).map_err(|error| {
815 Report::msg(format!("failed to decode package '{}': {error}", path.display()))
816 })?;
817 Ok(Arc::new(package))
818}
819
820fn package_requirements(
821 package: &MastPackage,
822) -> BTreeMap<PackageId, PreassembledDependencyMetadata> {
823 package
824 .manifest
825 .dependencies()
826 .map(|dependency| {
827 (
828 dependency.name.clone(),
829 PreassembledDependencyMetadata {
830 version: PackageVersion::new(dependency.version.clone(), dependency.digest),
831 kind: dependency.kind,
832 },
833 )
834 })
835 .collect()
836}