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