Skip to main content

miden_project/
lib.rs

1#![no_std]
2
3#[macro_use]
4extern crate alloc;
5
6#[cfg(any(test, feature = "std"))]
7extern crate std;
8
9#[cfg(feature = "serde")]
10pub mod ast;
11mod dependencies;
12mod linkage;
13mod package;
14mod profile;
15mod target;
16#[cfg(all(test, feature = "std", feature = "serde"))]
17mod tests;
18mod workspace;
19
20use alloc::{sync::Arc, vec::Vec};
21
22#[cfg(feature = "serde")]
23use miden_assembly_syntax::{
24    Report,
25    debuginfo::{SourceFile, SourceId},
26    diagnostics::{Label, RelatedError, RelatedLabel},
27};
28// Re-exported for consistency
29pub use miden_assembly_syntax::{Word, debuginfo::Uri, semver};
30use miden_assembly_syntax::{
31    debuginfo::{SourceSpan, Span},
32    diagnostics::{Diagnostic, miette},
33};
34pub use miden_core::LexicographicWord;
35pub use miden_mast_package::TargetType;
36#[cfg(feature = "serde")]
37use serde::{Deserialize, Serialize};
38pub use toml::Value;
39
40pub use self::{
41    dependencies::*, linkage::Linkage, package::Package, profile::Profile, target::Target,
42    workspace::Workspace,
43};
44
45/// An alias for [`alloc::collections::BTreeMap`].
46pub type Map<K, V> = alloc::collections::BTreeMap<K, V>;
47
48/// Represents arbitrary metadata in key/value format
49///
50/// This representation provides spans for both keys and values
51pub type Metadata = Map<Span<Arc<str>>, Span<Value>>;
52
53/// Represents a set of named metadata tables, where each table is represented by [Metadata].
54///
55/// This representation provides spans for the table name, and each entry in that table's metadata.
56pub type MetadataSet = Map<Span<Arc<str>>, Metadata>;
57
58/// Represents any Miden project type, i.e. either a workspace, or a standalone package.
59#[derive(Debug, Clone)]
60pub enum Project {
61    /// A specific member of a Miden workspace
62    WorkspacePackage {
63        /// The member package
64        package: Arc<Package>,
65        /// The containing Miden workspace
66        workspace: Arc<Workspace>,
67    },
68    /// A standalone Miden package
69    Package(Arc<Package>),
70}
71
72impl From<alloc::boxed::Box<Package>> for Project {
73    fn from(value: alloc::boxed::Box<Package>) -> Self {
74        Self::Package(value.into())
75    }
76}
77
78impl From<Arc<Package>> for Project {
79    fn from(value: Arc<Package>) -> Self {
80        Self::Package(value)
81    }
82}
83
84impl Project {
85    /// Returns true if this project is a member of a workspace
86    pub fn is_workspace_member(&self) -> bool {
87        matches!(self, Self::WorkspacePackage { .. })
88    }
89
90    /// Get the underlying [Package] for this project
91    pub fn package(&self) -> Arc<Package> {
92        match self {
93            Self::WorkspacePackage { package, .. } | Self::Package(package) => Arc::clone(package),
94        }
95    }
96
97    /// Returns the manifest from which this project was loaded
98    #[cfg(feature = "std")]
99    pub fn manifest_path(&self) -> Option<&std::path::Path> {
100        match self {
101            Self::WorkspacePackage { package, .. } | Self::Package(package) => {
102                package.manifest_path()
103            },
104        }
105    }
106}
107
108/// Parsing
109#[cfg(all(feature = "std", feature = "serde"))]
110impl Project {
111    /// Load a project manifest from `path`.
112    ///
113    /// If the given manifest source belongs to a package within a larger workspace, this function
114    /// will attempt to resolve the workspace and extract the package from it.
115    pub fn load(
116        path: impl AsRef<std::path::Path>,
117        source_manager: &dyn miden_assembly_syntax::debuginfo::SourceManager,
118    ) -> Result<Self, Report> {
119        let path = path.as_ref();
120        let manifest_path = if path.is_dir() {
121            path.join("miden-project.toml").canonicalize().map_err(Report::msg)?
122        } else {
123            path.canonicalize().map_err(Report::msg)?
124        };
125
126        Self::try_load_as_workspace_member(None, &manifest_path, source_manager)
127    }
128
129    /// Load a project manifest from `path`, expected to be named `name`
130    ///
131    /// If the given manifest source belongs to a package within a larger workspace, this function
132    /// will attempt to resolve the workspace and extract the package from it.
133    pub fn load_project_reference(
134        name: &str,
135        path: impl AsRef<std::path::Path>,
136        source_manager: &dyn miden_assembly_syntax::debuginfo::SourceManager,
137    ) -> Result<Self, Report> {
138        let path = path.as_ref();
139        let manifest_path = if path.is_dir() {
140            path.join("miden-project.toml").canonicalize().map_err(Report::msg)?
141        } else {
142            path.canonicalize().map_err(Report::msg)?
143        };
144
145        Self::try_load_as_workspace_member(Some(name), &manifest_path, source_manager)
146    }
147
148    fn try_load_as_workspace_member(
149        name: Option<&str>,
150        manifest_path: impl AsRef<std::path::Path>,
151        source_manager: &dyn miden_assembly_syntax::debuginfo::SourceManager,
152    ) -> Result<Self, Report> {
153        use miden_assembly_syntax::debuginfo::SourceManagerExt;
154
155        let manifest_path = manifest_path.as_ref();
156        let ancestors = manifest_path
157            .parent()
158            .ok_or_else(|| {
159                Report::msg(format!(
160                    "manifest '{}' has no parent directory",
161                    manifest_path.display()
162                ))
163            })?
164            .ancestors();
165
166        let initial_package_dir = manifest_path.parent();
167        for ancestor in ancestors {
168            let workspace_manifest = ancestor.join("miden-project.toml");
169            if !workspace_manifest.exists() {
170                continue;
171            }
172
173            let source = source_manager.load_file(&workspace_manifest).map_err(Report::msg)?;
174
175            let contents = toml::from_str::<toml::Table>(source.as_str()).map_err(|err| {
176                Report::msg(format!("could not parse {}: {err}", workspace_manifest.display()))
177            })?;
178            if contents.contains_key("workspace") {
179                let workspace = Workspace::load(source, source_manager)?;
180                let package = if let Some(package) = workspace
181                    .members()
182                    .iter()
183                    .find(|member| member.manifest_path().is_some_and(|path| path == manifest_path))
184                    .cloned()
185                {
186                    package
187                } else if manifest_path == workspace_manifest {
188                    let Some(name) = name else {
189                        break;
190                    };
191                    workspace.get_member_by_name(name).ok_or_else(|| {
192                        Report::msg(format!(
193                            "workspace '{}' does not contain a member named '{name}'",
194                            workspace_manifest.display(),
195                        ))
196                    })?
197                } else {
198                    break;
199                };
200
201                return Ok(Self::WorkspacePackage { package, workspace: workspace.into() });
202            } else if Some(ancestor) != initial_package_dir {
203                break;
204            }
205        }
206
207        let source = source_manager.load_file(manifest_path).map_err(Report::msg)?;
208        let package = Package::load(source)?;
209        Ok(Self::Package(package.into()))
210    }
211}
212
213/// A utility function for making a path absolute and canonical.
214///
215/// Relative paths are made absolute relative to `workspace_root`.
216#[cfg(all(feature = "std", feature = "serde"))]
217pub(crate) fn absolutize_path(
218    path: &std::path::Path,
219    workspace_root: &std::path::Path,
220) -> Result<std::path::PathBuf, std::io::Error> {
221    if path.is_absolute() {
222        path.canonicalize()
223    } else {
224        workspace_root.join(path).canonicalize()
225    }
226}