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