Skip to main content

miden_assembly/
project.rs

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
35// ASSEMBLER EXTENSIONS
36// ================================================================================================
37
38impl Assembler {
39    /// Get a [ProjectAssembler] configured for the project whose manifest is at `manifest_path`.
40    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    /// Get a [ProjectAssembler] configured for the project whose manifest is at `manifest_path`.
53    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    /// Get a [ProjectAssembler] configured for `project`
79    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    /// Get a [ProjectAssembler] configured for `project`
92    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
114// PROJECT ASSEMBLER
115// ================================================================================================
116
117pub 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    /// Get the project being assembled
211    pub fn project(&self) -> &ProjectPackage {
212        self.project.as_ref()
213    }
214
215    /// Assemble a target of the current project matching `target_selector`, using the configuration
216    /// under the profile `profile_name`.
217    ///
218    /// Returns an error if:
219    ///
220    /// * `target_selector` cannot find a matching target in the current project
221    /// * `profile_name` is not a profile defined in this project
222    /// * An error occurs during assembly of the project or one of its dependencies
223    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        // When building an executable target from a project with a library target, we require
231        // that the executable target be linked statically against the library target
232        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    /// This is a low-level utility function of the project assembly infrastructure for assembling
266    /// a project target whose sources and source provenance may have already been computed by the
267    /// caller.
268    ///
269    /// In almost all cases you should prefer to use [`ProjectAssembler::assemble`].
270    ///
271    /// Callers are required to uphold the following invariants:
272    ///
273    /// * `package_id` must be the package identifier for `project` in the current dependency graph
274    /// * `target` must be a target extracted from `project`
275    /// * `required_lib` must be set if assembly of `target` would depend on another target of the
276    ///   same project (i.e. an executable target implicitly depends on the library target).
277    ///
278    /// It is invalid to provide `source_provenance` without `sources` - doing so will trigger an
279    /// assertion.
280    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                // We do not link the package here, as by definition a required library is only
312                // present for executable targets, and we always unconditionally link kernel
313                // dependencies just prior to assembling the package
314                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        // Collect specific well-known custom sections produced by the project assembler
350        let mut sections = Vec::new();
351
352        // Section: build provenance
353        //
354        // This is produced before actual assembly, while we still have the sources on hand
355        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        // We don't apply post-assembly hooks when assembling manually-provided sources
403        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// ================================================================================================
795
796#[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
831// HELPER FUNCTIONS
832// ================================================================================================
833
834fn 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}