Skip to main content

miden_assembly/project/
providers.rs

1mod masm;
2
3use alloc::borrow::Cow;
4use core::ops::ControlFlow;
5
6use miden_assembly_syntax::debuginfo::SourceManager;
7use miden_package_registry::PackageRegistryAndProvider;
8use miden_project::ProjectDependencyGraph;
9
10pub use self::masm::MasmSourceProvider;
11use super::*;
12
13/// This struct provides important context about the current target being assembled to
14/// implementations of the [ProjectSourceProvider] trait.
15pub struct TargetAssemblyContext<'a> {
16    /// The package manifest for the target being assembled
17    pub package: Arc<ProjectPackage>,
18    /// The resolved/canonicalized package manifest path
19    ///
20    /// NOTE: This will be set to an empty path for virtual project manifests
21    pub manifest_path: &'a std::path::Path,
22    /// The resolved/canonicalized path to the directory containing `manifest_path`, or the parent
23    /// of `resolved_target_root` for virtual projects.
24    pub project_root: Cow<'a, std::path::Path>,
25    /// The resolved/canonicalized path to the root source file of `target`
26    pub resolved_target_root: Box<std::path::Path>,
27    /// The target being assembled
28    pub target: &'a Target,
29    /// The build profile selected for this assembly session
30    pub profile: &'a Profile,
31    /// The dependency graph computed for this assembly session
32    pub dependency_graph: &'a ProjectDependencyGraph,
33    /// The current source manager
34    pub source_manager: Arc<dyn SourceManager>,
35    /// The current package store of the assembler
36    pub package_registry: &'a dyn PackageRegistryAndProvider,
37    /// The assembler-wide `warnings_as_errors` flag
38    pub warnings_as_errors: bool,
39}
40
41impl<'a> TargetAssemblyContext<'a> {
42    pub fn new(
43        package: Arc<ProjectPackage>,
44        manifest_path: &'a std::path::Path,
45        target: &'a Target,
46        profile: &'a Profile,
47        dependency_graph: &'a ProjectDependencyGraph,
48        package_registry: &'a dyn PackageRegistryAndProvider,
49        source_manager: Arc<dyn SourceManager>,
50    ) -> Result<Self, Report> {
51        let project_root = manifest_path.parent().ok_or_else(|| {
52            Report::msg(format!("manifest '{}' has no parent directory", manifest_path.display()))
53        })?;
54        let target_path = target.path.to_path().ok_or_else(|| {
55            Report::msg(format!(
56                "invalid target '{}': '{}' is not a valid file path",
57                target.name.inner(),
58                target.path
59            ))
60        })?;
61        let root_path = project_root.join(&target_path);
62        let root_path = root_path.canonicalize().map_err(|error| {
63            Report::msg(format!(
64                "failed to resolve target source '{}': {error}",
65                root_path.display()
66            ))
67        })?;
68        Ok(TargetAssemblyContext {
69            package,
70            manifest_path,
71            project_root: project_root.into(),
72            resolved_target_root: root_path.into_boxed_path(),
73            target,
74            profile,
75            dependency_graph,
76            source_manager,
77            package_registry,
78            warnings_as_errors: false,
79        })
80    }
81
82    pub fn new_virtual(
83        package: Arc<ProjectPackage>,
84        target: &'a Target,
85        profile: &'a Profile,
86        dependency_graph: &'a ProjectDependencyGraph,
87        package_registry: &'a dyn PackageRegistryAndProvider,
88        source_manager: Arc<dyn SourceManager>,
89    ) -> Result<Self, Report> {
90        let target_path = target.path.to_path().ok_or_else(|| {
91            Report::msg(format!(
92                "invalid target '{}': '{}' is not a valid file path",
93                target.name.inner(),
94                target.path
95            ))
96        })?;
97        let target_path = target_path.canonicalize().unwrap_or(target_path.clone());
98        let resolved_target_root = target_path.clone().into_boxed_path();
99        let project_root = target_path
100            .parent()
101            .map(std::path::Path::to_path_buf)
102            .unwrap_or(PathBuf::from("."));
103        Ok(TargetAssemblyContext {
104            package,
105            manifest_path: std::path::Path::new(""),
106            project_root: project_root.into(),
107            resolved_target_root,
108            target,
109            profile,
110            dependency_graph,
111            source_manager,
112            package_registry,
113            warnings_as_errors: false,
114        })
115    }
116
117    #[inline]
118    pub fn with_warnings_as_errors(&mut self, yes: bool) -> &mut Self {
119        self.warnings_as_errors = yes;
120        self
121    }
122}
123
124/// This trait provides source file inputs and package post-processing hooks for a Miden Assembly
125/// project, regardless of the source language it was derived from.
126///
127/// For Miden Assembly source projects this is straightforward, see [MasmSourceProvider].
128///
129/// For languages other than MASM, which require a compilation step to produce Miden Assembly AST
130/// from the source language prior to assembly, and may have differing means for providing package
131/// metadata (advice data, account component metadata, custom sections), this trait provides the
132/// necessary hooks so that the project assembler can request compilation of a project in source
133/// form on-demand. Implementors are given all available information needed to compile to MASM, and
134/// are expected to return requested artifacts to the project assembler.
135///
136/// Source providers are registered by the file type (i.e. file extension used by the source file)
137/// with the assembler when it is created. Only one source provider per-file-type is allowed.
138pub trait ProjectSourceProvider {
139    /// Returns the file extension this provider should be registered as handling, e.g. `rs`
140    fn file_type(&self) -> &'static str;
141    /// Called to request the compiled/parsed Miden Assembly AST corresponding to the current target
142    /// being assembled.
143    fn provide_sources(
144        &self,
145        context: &TargetAssemblyContext<'_>,
146    ) -> Result<ProjectSourceInputs, Report>;
147
148    /// Same as `provide_sources`, but allows the provider to interrupt the build and exit early
149    fn provide_sources_interruptible(
150        &self,
151        context: &TargetAssemblyContext<'_>,
152    ) -> Result<ControlFlow<(), ProjectSourceInputs>, Report> {
153        self.provide_sources(context).map(ControlFlow::Continue)
154    }
155
156    /// Called to request the source files that are inputs to assembly of the current target, so
157    /// that source provenance hash for the target can be computed.
158    ///
159    /// It is expected that all source files that contribute to the build be included in the set
160    /// of source inputs returned, otherwise package identity for the assembled target will be
161    /// incomplete, and another instance of the same package may be used from the cache if the
162    /// source provenance appears unchanged, even when the artifacts produced would be different.
163    ///
164    /// For MASM packages, the above is already guaranteed - but for compilation of packages in
165    /// other languages, such as Rust, the toolchain invoking the assembler must ensure that all
166    /// build inputs are accounted for. Note that you _do not_ need to include the sources of
167    /// your Miden dependencies, and non-Miden dependencies can be accounted for by hashing a
168    /// dependency lock file if present (e.g. `Cargo.toml`).
169    fn provide_source_provenance(
170        &self,
171        context: &TargetAssemblyContext<'_>,
172    ) -> Result<ProjectSourceProvenanceInputs, Report>;
173
174    /// Called after a project target - whose sources were provided via this trait- has been
175    /// assembled to a package, so that the provider can do any language-specific post-processing
176    /// of the assembled package before it is frozen and submitted to the package cache/registry.
177    ///
178    /// The default implementation is a no-op.
179    ///
180    /// The `context` given is the same as given to [`ProjectSourceProvider::provide_sources`].
181    #[allow(unused_variables)]
182    fn post_process_package(
183        &self,
184        package: &mut MastPackage,
185        context: &TargetAssemblyContext<'_>,
186    ) -> Result<(), Report> {
187        Ok(())
188    }
189}