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}