Skip to main content

miden_project/
workspace.rs

1#[cfg(feature = "std")]
2use std::{boxed::Box, path::Path, string::ToString};
3
4#[cfg(all(feature = "std", feature = "serde"))]
5use miden_assembly_syntax::debuginfo::SourceManager;
6
7use crate::*;
8
9/// Represents a Miden project workspace.
10///
11/// Workspaces are comprised of one or more sub-projects that define the member packages of the
12/// workspace.
13#[derive(Debug)]
14pub struct Workspace {
15    /// The file path of the workspace manifest, if applicable.
16    #[cfg(feature = "std")]
17    manifest_path: Option<Box<Path>>,
18    /// The set of packages which are direct members of this workspace
19    members: Vec<Arc<Package>>,
20}
21
22/// Accessors
23impl Workspace {
24    /// Return the path of the workspace manifest, if known.
25    #[cfg(feature = "std")]
26    pub fn manifest_path(&self) -> Option<&Path> {
27        self.manifest_path.as_deref()
28    }
29
30    /// Return the path of the directory containing the workspace manifest
31    #[cfg(feature = "std")]
32    pub fn workspace_root(&self) -> Option<&Path> {
33        self.manifest_path()?.parent()
34    }
35
36    /// Get the set of packages which are members of this workspace
37    pub fn members(&self) -> &[Arc<Package>] {
38        &self.members
39    }
40
41    /// Look up a workspace member by its package name
42    pub fn get_member_by_name(&self, name: impl AsRef<str>) -> Option<Arc<Package>> {
43        let name = name.as_ref();
44        self.members().iter().find(|member| &**member.name().inner() == name).cloned()
45    }
46
47    /// Look up a workspace member by its workspace-relative path
48    #[cfg(feature = "std")]
49    pub fn get_member_by_relative_path(&self, path: impl AsRef<Path>) -> Option<Arc<Package>> {
50        let path = path.as_ref();
51        let path = self.workspace_root()?.join(path);
52        self.members()
53            .iter()
54            .find(|member| {
55                member.manifest_path().is_some_and(|p| p.parent() == Some(path.as_path()))
56            })
57            .cloned()
58    }
59}
60
61/// Parsing
62#[cfg(all(feature = "std", feature = "serde"))]
63impl Workspace {
64    /// Load a [Workspace] from `source`, using the provided `source_manager` when loading the
65    /// sources of workspace members.
66    pub fn load(
67        source: Arc<SourceFile>,
68        source_manager: &dyn SourceManager,
69    ) -> Result<Box<Self>, Report> {
70        use miden_assembly_syntax::debuginfo::SourceManagerExt;
71
72        use crate::ast::ProjectFileError;
73
74        let mut file = ast::WorkspaceFile::parse(source.clone())?;
75
76        let manifest_uri = source.content().uri();
77        let manifest_path = if manifest_uri.scheme().is_none_or(|scheme| scheme == "file") {
78            Some(Path::new(manifest_uri.path()).to_path_buf().into_boxed_path())
79        } else {
80            None
81        };
82
83        let members = core::mem::take(&mut file.workspace.members);
84
85        let mut workspace = Box::new(Workspace {
86            manifest_path: manifest_path.clone(),
87            members: Vec::with_capacity(members.len()),
88        });
89
90        for member in members {
91            let Some(workspace_root) = workspace.workspace_root() else {
92                return Err(ProjectFileError::LoadWorkspaceMemberFailed {
93                    source_file: source.clone(),
94                    span: Label::new(
95                        member.span(),
96                        "cannot load workspace members for virtual workspace manifest: manifest path must be resolvable",
97                    ),
98                }
99                .into());
100            };
101            let relative_path = Path::new(member.as_str());
102            let member_dir =
103                crate::absolutize_path(relative_path, workspace_root).map_err(|err| {
104                    ProjectFileError::LoadWorkspaceMemberFailed {
105                        source_file: source.clone(),
106                        span: Label::new(member.span(), err.to_string()),
107                    }
108                })?;
109            if member_dir.strip_prefix(workspace_root).is_err() {
110                return Err(ProjectFileError::LoadWorkspaceMemberFailed {
111                    source_file: source.clone(),
112                    span: Label::new(
113                        member.span(),
114                        "workspace members must be located within the workspace root",
115                    ),
116                }
117                .into());
118            }
119            let manifest_path = member_dir.join("miden-project.toml");
120            let member_manifest = source_manager.load_file(&manifest_path).map_err(|err| {
121                ProjectFileError::LoadWorkspaceMemberFailed {
122                    source_file: source.clone(),
123                    span: Label::new(member.span(), err.to_string()),
124                }
125            })?;
126            let package = Package::load_from_workspace(member_manifest, &file)?;
127            workspace.members.push(Arc::from(package));
128        }
129
130        Ok(workspace)
131    }
132}