miden_project/
workspace.rs1#[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#[derive(Debug)]
14pub struct Workspace {
15 #[cfg(feature = "std")]
17 manifest_path: Option<Box<Path>>,
18 members: Vec<Arc<Package>>,
20}
21
22impl Workspace {
24 #[cfg(feature = "std")]
26 pub fn manifest_path(&self) -> Option<&Path> {
27 self.manifest_path.as_deref()
28 }
29
30 #[cfg(feature = "std")]
32 pub fn workspace_root(&self) -> Option<&Path> {
33 self.manifest_path()?.parent()
34 }
35
36 pub fn members(&self) -> &[Arc<Package>] {
38 &self.members
39 }
40
41 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 #[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#[cfg(all(feature = "std", feature = "serde"))]
63impl Workspace {
64 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}