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::{ast::ModuleKind, diagnostics::Report};
8use miden_mast_package::{Package as MastPackage, TargetType};
9use miden_package_registry::{PackageCache, PackageId, Version as PackageVersion};
10use miden_project::{
11 Linkage, Package as ProjectPackage, PreassembledDependencyMetadata, Profile,
12 ProjectDependencyNodeProvenance, ProjectSource, ProjectSourceOrigin, Target,
13};
14
15use crate::{Assembler, ast::Module};
16
17mod build_provenance;
18mod dependency_graph;
19mod providers;
20mod runtime_dependencies;
21mod target_selector;
22
23use self::{
24 build_provenance::PackageBuildProvenance, dependency_graph::DependencyGraph,
25 runtime_dependencies::RuntimeDependencies,
26};
27pub use self::{
28 providers::{MasmSourceProvider, ProjectSourceProvider, TargetAssemblyContext},
29 target_selector::ProjectTargetSelector,
30};
31
32#[cfg(test)]
33mod tests;
34
35impl Assembler {
39 pub fn for_project_at_path<'a, S>(
41 self,
42 manifest_path: impl AsRef<FsPath>,
43 store: &'a mut S,
44 ) -> Result<ProjectAssembler<'a, S>, Report>
45 where
46 S: PackageCache,
47 {
48 let masm_provider = Box::new(MasmSourceProvider) as Box<_>;
49 self.for_project_at_path_with_providers(manifest_path, store, [masm_provider])
50 }
51
52 pub fn for_project_at_path_with_providers<'a, S>(
54 self,
55 manifest_path: impl AsRef<FsPath>,
56 store: &'a mut S,
57 providers: impl IntoIterator<Item = Box<dyn ProjectSourceProvider>>,
58 ) -> Result<ProjectAssembler<'a, S>, Report>
59 where
60 S: PackageCache,
61 {
62 let manifest_path = manifest_path.as_ref();
63 let source_manager = self.source_manager();
64 let project = miden_project::Project::load(manifest_path, &source_manager)?;
65 let package = project.package();
66 let dependency_graph =
67 DependencyGraph::from_project_path(manifest_path, store, source_manager)?;
68
69 Ok(ProjectAssembler {
70 assembler: self,
71 project: package,
72 source_provider: SourceProviderRegistry::new(providers),
73 dependency_graph,
74 store,
75 })
76 }
77
78 pub fn for_project<'a, S>(
80 self,
81 project: Arc<ProjectPackage>,
82 store: &'a mut S,
83 ) -> Result<ProjectAssembler<'a, S>, Report>
84 where
85 S: PackageCache,
86 {
87 let masm_provider = Box::new(MasmSourceProvider) as Box<_>;
88 self.for_project_with_providers(project, store, [masm_provider])
89 }
90
91 pub fn for_project_with_providers<'a, S>(
93 self,
94 project: Arc<ProjectPackage>,
95 store: &'a mut S,
96 providers: impl IntoIterator<Item = Box<dyn ProjectSourceProvider>>,
97 ) -> Result<ProjectAssembler<'a, S>, Report>
98 where
99 S: PackageCache,
100 {
101 let source_manager = self.source_manager();
102 let dependency_graph =
103 DependencyGraph::from_project(project.clone(), store, source_manager)?;
104 Ok(ProjectAssembler {
105 assembler: self,
106 project,
107 source_provider: SourceProviderRegistry::new(providers),
108 dependency_graph,
109 store,
110 })
111 }
112}
113
114pub struct ProjectSourceInputs {
118 pub root: Box<Module>,
119 pub support: Vec<Box<Module>>,
120}
121
122pub struct ProjectSourceProvenanceInputs {
123 pub root: SourceFileProvenance,
124 pub support: Vec<SourceFileProvenance>,
125}
126
127pub struct SourceFileProvenance {
128 pub path: Box<std::path::Path>,
129 pub content: Box<str>,
130}
131
132impl SourceFileProvenance {
133 pub fn from_path(path: PathBuf) -> Result<Self, Report> {
134 let content = fs::read_to_string(&path).map_err(|err| {
135 Report::msg(format!("unable to read source file '{}': {err}", path.display()))
136 })?;
137 Ok(Self {
138 path: path.into_boxed_path(),
139 content: content.into_boxed_str(),
140 })
141 }
142}
143
144pub struct SourceProviderRegistry {
145 registered: BTreeMap<&'static str, Box<dyn ProjectSourceProvider>>,
146}
147
148impl Default for SourceProviderRegistry {
149 fn default() -> Self {
150 Self {
151 registered: BTreeMap::from_iter([(
152 "masm",
153 Box::new(MasmSourceProvider) as Box<dyn ProjectSourceProvider>,
154 )]),
155 }
156 }
157}
158
159impl SourceProviderRegistry {
160 pub fn new(providers: impl IntoIterator<Item = Box<dyn ProjectSourceProvider>>) -> Self {
161 let mut this = Self {
162 registered: providers.into_iter().map(|p| (p.file_type(), p)).collect(),
163 };
164
165 if !this.registered.contains_key("masm") {
166 this.registered.insert("masm", Box::new(MasmSourceProvider));
167 }
168
169 this
170 }
171
172 pub fn with_source_provider(
173 &mut self,
174 provider: impl ProjectSourceProvider + 'static,
175 ) -> &mut Self {
176 let file_type = provider.file_type();
177 let provider = Box::new(provider) as Box<dyn ProjectSourceProvider>;
178
179 self.registered.insert(file_type, provider);
180
181 self
182 }
183
184 #[inline]
185 pub fn get_provider(&self, file_type: &str) -> Option<&dyn ProjectSourceProvider> {
186 self.registered.get(file_type).map(AsRef::as_ref)
187 }
188}
189
190pub struct ProjectAssembler<'a, S: PackageCache> {
191 assembler: Assembler,
192 project: Arc<ProjectPackage>,
193 dependency_graph: DependencyGraph,
194 source_provider: SourceProviderRegistry,
195 store: &'a mut S,
196}
197
198impl<'a, S> ProjectAssembler<'a, S>
199where
200 S: PackageCache,
201{
202 pub fn with_source_provider(
203 &mut self,
204 provider: impl ProjectSourceProvider + 'static,
205 ) -> &mut Self {
206 self.source_provider.with_source_provider(provider);
207 self
208 }
209
210 pub fn project(&self) -> &ProjectPackage {
212 self.project.as_ref()
213 }
214
215 pub fn assemble(
224 &mut self,
225 target_selector: ProjectTargetSelector<'_>,
226 profile_name: &str,
227 ) -> Result<Arc<MastPackage>, Report> {
228 let target = target_selector.select_target(self.project.as_ref())?;
229
230 let mut cache = BTreeMap::new();
233 let root_id = self.dependency_graph.root().clone();
234 let required_lib = if target.is_executable()
235 && let Some(library_target) =
236 self.project.library_target().map(|target| target.inner().clone())
237 {
238 Some(self.assemble_source_package(
239 root_id.clone(),
240 Arc::clone(&self.project),
241 &library_target,
242 profile_name,
243 None,
244 None,
245 None,
246 &mut cache,
247 )?)
248 } else {
249 None
250 };
251
252 self.assemble_source_package(
253 root_id,
254 Arc::clone(&self.project),
255 &target,
256 profile_name,
257 required_lib,
258 None,
259 None,
260 &mut cache,
261 )
262 .map(|resolved| resolved.package)
263 }
264
265 pub fn assemble_source_package(
281 &mut self,
282 package_id: PackageId,
283 project: Arc<ProjectPackage>,
284 target: &Target,
285 profile_name: &str,
286 required_lib: Option<ResolvedPackage>,
287 sources: Option<ProjectSourceInputs>,
288 source_provenance: Option<ProjectSourceProvenanceInputs>,
289 cache: &mut BTreeMap<PackageId, ResolvedPackage>,
290 ) -> Result<ResolvedPackage, Report> {
291 assert!(
292 source_provenance.is_none() || sources.is_some(),
293 "source provenance may only be provided with sources"
294 );
295
296 let cache_key = project.target_package_name(target);
297 if let Some(package) = cache.get(&cache_key).cloned() {
298 assert_eq!(package.package.kind, target.ty);
299 return Ok(package);
300 }
301
302 let profile = project.resolve_profile(profile_name)?;
303 let mut assembler = self.assembler.clone().with_profile(profile);
304 let mut runtime_dependencies = RuntimeDependencies::default();
305 debug_assert!(
306 required_lib.is_none() || target.ty.is_executable(),
307 "expected required_lib only for executable targets"
308 );
309 match required_lib {
310 Some(required_lib) if required_lib.package.is_kernel() => {
311 runtime_dependencies.record_linked_kernel_dependency(required_lib.package)?;
315 },
316 Some(required_lib) => {
317 assembler.link_package(required_lib.package.clone(), Linkage::Static)?;
318 if let Some(kernel_package) = required_lib.linked_kernel_package {
319 runtime_dependencies.record_linked_kernel_dependency(kernel_package)?;
320 }
321 },
322 None => (),
323 }
324
325 let node = self.dependency_graph.get(&package_id)?;
326 let dependencies = node.dependencies.clone();
327 for edge in dependencies.iter() {
328 let dependency_package =
329 self.resolve_dependency_package(&edge.dependency, profile_name, cache)?;
330 if !dependency_package.package.is_library() {
331 return Err(Report::msg(format!(
332 "dependency '{}' resolved to executable package '{}', but only library-like packages can be linked",
333 edge.dependency, dependency_package.package.name
334 )));
335 }
336
337 if !dependency_package.package.is_kernel() {
338 assembler.link_package(dependency_package.package.clone(), edge.linkage)?;
339 }
340 runtime_dependencies.merge_package(dependency_package, edge.linkage)?;
341 }
342
343 let manually_provided_sources = sources.is_some();
344 let ProjectSourceInputs { root, support } = match sources {
345 Some(sources) => sources,
346 None => self.load_target_sources(project.clone(), target, profile)?,
347 };
348
349 let mut sections = Vec::new();
351
352 let build_provenance = if source_provenance.is_some() || !manually_provided_sources {
356 self.dependency_graph.build_source_provenance(
357 &package_id,
358 project.clone(),
359 target,
360 profile_name,
361 &self.source_provider,
362 self.store,
363 source_provenance.as_ref(),
364 )?
365 } else {
366 None
367 };
368 if let Some(build_provenance) = build_provenance {
369 sections.push(build_provenance.to_section());
370 }
371
372 if let Some(kernel_package) = runtime_dependencies.kernel.clone() {
373 if matches!(target.ty, TargetType::Kernel) {
374 return Err(Report::msg(format!(
375 "kernel targets cannot depend on a kernel, dependency '{}' is a kernel",
376 kernel_package.name
377 )));
378 }
379 assembler.link_package(kernel_package, Linkage::Dynamic)?;
380 }
381
382 let mut product = match target.ty {
383 TargetType::Executable => {
384 assembler.assemble_executable_modules(package_id.clone(), root, support)?
385 },
386 _ if target.ty.is_library() => {
387 assembler.assemble_library_modules(package_id.clone(), root, support, target.ty)?
388 },
389 _ => unreachable!("non-exhaustive target type"),
390 };
391
392 product
393 .extend_dependencies(runtime_dependencies.deps.into_values())
394 .expect("assembled package manifest should have unique runtime dependencies");
395
396 let mut package = product.into_artifact()?;
397 package.name = project.target_package_name(target);
398 package.version = project.version().into_inner().clone();
399 package.description = project.description().map(|description| description.to_string());
400 package.sections.extend(sections);
401
402 if !manually_provided_sources {
404 self.apply_post_assembly_hooks(&mut package, project.clone(), target, profile)?;
405 }
406
407 let package = Arc::from(package);
408
409 let resolved = ResolvedPackage {
410 package,
411 linked_kernel_package: runtime_dependencies.kernel,
412 };
413 cache.insert(package_id, resolved.clone());
414
415 Ok(resolved)
416 }
417
418 fn resolve_dependency_package(
419 &mut self,
420 package_id: &PackageId,
421 profile_name: &str,
422 cache: &mut BTreeMap<PackageId, ResolvedPackage>,
423 ) -> Result<ResolvedPackage, Report> {
424 if let Some(package) = cache.get(package_id).cloned() {
425 return Ok(package);
426 }
427
428 let node = self.dependency_graph.get(package_id)?;
429 let node_version = node.version.clone();
430
431 let (package, should_cache) = match &node.provenance {
432 ProjectDependencyNodeProvenance::Source(ProjectSource::Virtual { .. }) => {
433 return Err(Report::msg(format!(
434 "package '{package_id}' is missing a manifest path",
435 )));
436 },
437 ProjectDependencyNodeProvenance::Source(ProjectSource::Real {
438 manifest_path,
439 origin,
440 library_path: Some(_),
441 ..
442 }) => {
443 let project = miden_project::Project::load_project_reference(
444 package_id,
445 manifest_path,
446 &self.assembler.source_manager(),
447 )
448 .map(|project| project.package())?;
449 let target = project
450 .library_target()
451 .map(|target| target.inner().clone())
452 .ok_or_else(|| {
453 Report::msg(format!(
454 "dependency '{package_id}' does not define a library target"
455 ))
456 })?;
457 match self.try_reuse_registered_source_package(
458 package_id,
459 &node_version,
460 project.clone(),
461 &target,
462 profile_name,
463 origin,
464 manifest_path,
465 )? {
466 RegisteredSourcePackage::Loaded(package) => (
467 ResolvedPackage {
468 linked_kernel_package: self
469 .resolve_linked_kernel_package(package.clone())?,
470 package,
471 },
472 false,
473 ),
474 reuse => {
475 let package = self.assemble_source_package(
476 package_id.clone(),
477 project,
478 &target,
479 profile_name,
480 None,
481 None,
482 None,
483 cache,
484 )?;
485 match reuse {
486 RegisteredSourcePackage::Missing => (),
487 RegisteredSourcePackage::IndexedButUnreadable(expected) => {
488 let actual = PackageVersion::new(
489 package.package.version.clone(),
490 package.package.digest(),
491 );
492 if actual != expected {
493 return Err(Report::msg(format!(
494 "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"
495 )));
496 }
497 },
498 RegisteredSourcePackage::Loaded(_) => unreachable!(),
499 }
500 (package, true)
501 },
502 }
503 },
504 ProjectDependencyNodeProvenance::Source(_) => {
505 let package =
506 self.load_canonical_package(package_id, &node_version)?.ok_or_else(|| {
507 Report::msg(format!(
508 "dependency '{package_id}' version '{node_version}' was not found in the package registry"
509 ))
510 })?;
511 (
512 ResolvedPackage {
513 linked_kernel_package: self
514 .resolve_linked_kernel_package(package.clone())?,
515 package,
516 },
517 false,
518 )
519 },
520 ProjectDependencyNodeProvenance::Registry { selected, .. } => {
521 let package = self.store.load_package(package_id, selected)?;
522 (
523 ResolvedPackage {
524 linked_kernel_package: self
525 .resolve_linked_kernel_package(package.clone())?,
526 package,
527 },
528 false,
529 )
530 },
531 ProjectDependencyNodeProvenance::Preassembled {
532 path,
533 selected,
534 kind,
535 requirements,
536 } => {
537 let package = load_selected_preassembled_package(
538 path,
539 package_id,
540 selected,
541 *kind,
542 requirements,
543 )?;
544 let should_cache = self.should_cache_preassembled_package(package_id, selected);
545 (
546 ResolvedPackage {
547 linked_kernel_package: self
548 .resolve_linked_kernel_package(package.clone())?,
549 package,
550 },
551 should_cache,
552 )
553 },
554 };
555
556 if should_cache {
557 self.cache_resolved_package(&package)?;
558 }
559 cache.insert(package_id.clone(), package.clone());
560 Ok(package)
561 }
562
563 fn resolve_linked_kernel_package(
564 &self,
565 package: Arc<MastPackage>,
566 ) -> Result<Option<Arc<MastPackage>>, Report> {
567 if package.is_kernel() {
568 return Ok(Some(package));
569 }
570
571 let Some(kernel_dependency) = package.kernel_runtime_dependency()? else {
572 return Ok(None);
573 };
574
575 let version =
576 PackageVersion::new(kernel_dependency.version.clone(), kernel_dependency.digest);
577 if self.store.get_exact_version(&kernel_dependency.name, &version).is_some() {
578 match self.store.load_package(&kernel_dependency.name, &version) {
579 Ok(kernel_package) => {
580 if !kernel_package.is_kernel() {
581 return Err(Report::msg(format!(
582 "runtime kernel dependency '{}@{}#{}' resolved to non-kernel package '{}'",
583 kernel_dependency.name,
584 kernel_dependency.version,
585 kernel_dependency.digest,
586 kernel_package.name
587 )));
588 }
589 return Ok(Some(kernel_package));
590 },
591 Err(load_error) => {
592 if let Some(kernel_package) = package
593 .try_embedded_kernel_package()
594 .map(|kernel_package| kernel_package.map(Arc::from))?
595 {
596 return Ok(Some(kernel_package));
597 }
598 return Err(load_error);
599 },
600 }
601 }
602
603 package
604 .try_embedded_kernel_package()
605 .map(|kernel_package| kernel_package.map(Arc::from))
606 }
607
608 fn load_canonical_package(
609 &self,
610 package_id: &PackageId,
611 version: &miden_project::SemVer,
612 ) -> Result<Option<Arc<MastPackage>>, Report> {
613 let Some(record) = self.store.get_by_semver(package_id, version) else {
614 return Ok(None);
615 };
616 self.store.load_package(package_id, record.version()).map(Some)
617 }
618
619 fn try_reuse_registered_source_package(
620 &self,
621 package_id: &PackageId,
622 version: &miden_project::SemVer,
623 project: Arc<ProjectPackage>,
624 target: &Target,
625 profile_name: &str,
626 origin: &ProjectSourceOrigin,
627 manifest_path: &FsPath,
628 ) -> Result<RegisteredSourcePackage, Report> {
629 let Some(record) = self.store.get_by_semver(package_id, version) else {
630 return Ok(RegisteredSourcePackage::Missing);
631 };
632 let package = match self.store.load_package(package_id, record.version()) {
633 Ok(package) => package,
634 Err(_) => {
635 return Ok(RegisteredSourcePackage::IndexedButUnreadable(record.version().clone()));
636 },
637 };
638
639 let expected = self.dependency_graph.expected_source_provenance(
640 package_id,
641 project,
642 target,
643 profile_name,
644 origin,
645 manifest_path,
646 &self.source_provider,
647 self.store,
648 None,
649 )?;
650
651 match PackageBuildProvenance::from_package(&package)? {
652 Some(actual) if actual == expected => Ok(()),
653 Some(actual) => Err(Report::msg(format!(
654 "package '{}' version '{}' is already registered with different source provenance (expected {}, found {}); bump the semantic version",
655 package_id,
656 version,
657 expected.describe(),
658 actual.describe(),
659 ))),
660 None => Err(Report::msg(format!(
661 "package '{package_id}' version '{version}' is already registered, but the canonical artifact is missing source provenance; bump the semantic version"
662 ))),
663 }?;
664
665 Ok(RegisteredSourcePackage::Loaded(package))
666 }
667
668 fn should_cache_preassembled_package(
669 &self,
670 package_id: &PackageId,
671 selected: &PackageVersion,
672 ) -> bool {
673 let Some(record) = self.store.get_by_semver(package_id, &selected.version) else {
674 return true;
675 };
676 if record.version() != selected {
677 return false;
678 }
679
680 self.store.load_package(package_id, selected).is_err()
681 }
682
683 fn cache_resolved_package(&mut self, package: &ResolvedPackage) -> Result<(), Report> {
684 self.cache_package(package.package.clone())?;
685 if let Some(kernel_package) = package.linked_kernel_package.clone()
686 && self.should_cache_linked_kernel_package(kernel_package.as_ref())
687 {
688 self.cache_package(kernel_package)?;
689 }
690 Ok(())
691 }
692
693 fn should_cache_linked_kernel_package(&self, package: &MastPackage) -> bool {
694 let version = PackageVersion::new(package.version.clone(), package.digest());
695 let Some(record) = self.store.get_by_semver(&package.name, &package.version) else {
696 return true;
697 };
698 if record.version() != &version {
699 return false;
700 }
701
702 self.store.load_package(&package.name, &version).is_err()
703 }
704
705 fn cache_package(&mut self, package: Arc<MastPackage>) -> Result<(), Report> {
706 self.store
707 .cache_package(package)
708 .map(|_| ())
709 .map_err(|error| Report::msg(error.to_string()))
710 }
711
712 fn load_target_sources(
713 &self,
714 project: Arc<ProjectPackage>,
715 target: &Target,
716 profile: &Profile,
717 ) -> Result<ProjectSourceInputs, Report> {
718 let (provider, context) =
719 self.get_provider_and_target_assembly_context(&project, target, profile)?;
720
721 let inputs = provider.provide_sources(&context)?;
722 match target.ty {
723 TargetType::Executable if !inputs.root.kind().is_executable() => {
724 Err(Report::msg(format!(
725 "requested target type is executable, but root module provided to assembler for '{}' is {}",
726 project.name(),
727 inputs.root.kind()
728 )))
729 },
730 TargetType::Kernel if !inputs.root.kind().is_kernel() => Err(Report::msg(format!(
731 "requested target type is kernel, but root module provided to assembler for '{}' is {}",
732 project.name(),
733 inputs.root.kind()
734 ))),
735 _ if inputs.root.path() != target.namespace.inner().as_ref() => {
736 Err(Report::msg(format!(
737 "requested target namespace is '{}', but root module provided to assembler for '{}' is '{}'",
738 target.namespace,
739 project.name(),
740 inputs.root.path()
741 )))
742 },
743 _ => Ok(inputs),
744 }
745 }
746
747 fn apply_post_assembly_hooks(
748 &self,
749 package: &mut MastPackage,
750 project: Arc<ProjectPackage>,
751 target: &Target,
752 profile: &Profile,
753 ) -> Result<(), Report> {
754 let (provider, context) =
755 self.get_provider_and_target_assembly_context(&project, target, profile)?;
756
757 provider.post_process_package(package, &context)?;
758
759 Ok(())
760 }
761
762 fn get_provider_and_target_assembly_context<'this>(
763 &'this self,
764 project: &'this Arc<ProjectPackage>,
765 target: &'this Target,
766 profile: &'this Profile,
767 ) -> Result<(&'this dyn ProjectSourceProvider, TargetAssemblyContext<'this>), Report> {
768 let manifest_path = project.expect_manifest_path()?;
769 let mut context = TargetAssemblyContext::new(
770 project.clone(),
771 manifest_path,
772 target,
773 profile,
774 self.dependency_graph.as_ref(),
775 self.store,
776 self.assembler.source_manager(),
777 )?;
778 context.with_warnings_as_errors(self.assembler.warnings_as_errors());
779
780 let extension = context.resolved_target_root.extension().ok_or_else(|| {
781 Report::msg(format!(
782 "invalid target 'path' {}: path must have an extension",
783 context.resolved_target_root.display()
784 ))
785 })?;
786 let extension = extension.to_string_lossy();
787
788 let provider = self.source_provider.get_provider(extension.as_ref()).ok_or_else(|| Report::msg(format!("unsupported target file type '{extension}': no provider has been registered for that file type")))?;
789
790 Ok((provider, context))
791 }
792}
793
794#[derive(Clone)]
797pub struct ResolvedPackage {
798 pub package: Arc<MastPackage>,
799 pub linked_kernel_package: Option<Arc<MastPackage>>,
800}
801
802enum RegisteredSourcePackage {
803 Missing,
804 Loaded(Arc<MastPackage>),
805 IndexedButUnreadable(PackageVersion),
806}
807
808#[derive(Debug, Clone, PartialEq, Eq)]
809struct PackageBuildSettings {
810 emit_debug_info: bool,
811 trim_paths: bool,
812}
813
814impl PackageBuildSettings {
815 fn legacy() -> Self {
816 Self { emit_debug_info: true, trim_paths: false }
817 }
818
819 fn from_profile(profile: &Profile) -> Self {
820 Self {
821 emit_debug_info: profile.should_emit_debug_info(),
822 trim_paths: profile.should_trim_paths(),
823 }
824 }
825
826 fn is_legacy(&self) -> bool {
827 *self == Self::legacy()
828 }
829}
830
831fn load_selected_preassembled_package(
835 path: &FsPath,
836 expected_name: &PackageId,
837 selected: &PackageVersion,
838 expected_kind: TargetType,
839 expected_requirements: &BTreeMap<PackageId, PreassembledDependencyMetadata>,
840) -> Result<Arc<MastPackage>, Report> {
841 let package = load_package_from_path(path)?;
842 if &package.name != expected_name {
843 return Err(Report::msg(format!(
844 "preassembled dependency '{}' at '{}' resolved to package '{}'",
845 expected_name,
846 path.display(),
847 package.name
848 )));
849 }
850
851 let actual = PackageVersion::new(package.version.clone(), package.digest());
852 if &actual != selected {
853 return Err(Report::msg(format!(
854 "preassembled dependency '{}@{}' at '{}' no longer matches the dependency graph selection '{}'",
855 expected_name,
856 actual,
857 path.display(),
858 selected
859 )));
860 }
861
862 if package.kind != expected_kind {
863 return Err(Report::msg(format!(
864 "preassembled dependency '{}@{}' at '{}' no longer matches the dependency graph target kind '{}'",
865 expected_name,
866 actual,
867 path.display(),
868 expected_kind
869 )));
870 }
871
872 let actual_requirements = package_requirements(&package);
873 if &actual_requirements != expected_requirements {
874 return Err(Report::msg(format!(
875 "preassembled dependency '{}@{}' at '{}' no longer matches the dependency graph dependency requirements",
876 expected_name,
877 actual,
878 path.display()
879 )));
880 }
881
882 Ok(package)
883}
884
885fn load_package_from_path(path: &FsPath) -> Result<Arc<MastPackage>, Report> {
886 let bytes = fs::read(path)
887 .map_err(|error| Report::msg(format!("failed to read '{}': {error}", path.display())))?;
888 let package = MastPackage::read_from_bytes_trusted(&bytes).map_err(|error| {
889 Report::msg(format!("failed to decode package '{}': {error}", path.display()))
890 })?;
891 Ok(Arc::new(package))
892}
893
894fn package_requirements(
895 package: &MastPackage,
896) -> BTreeMap<PackageId, PreassembledDependencyMetadata> {
897 package
898 .manifest
899 .dependencies()
900 .map(|dependency| {
901 (
902 dependency.name.clone(),
903 PreassembledDependencyMetadata {
904 version: PackageVersion::new(dependency.version.clone(), dependency.digest),
905 kind: dependency.kind,
906 },
907 )
908 })
909 .collect()
910}