miden_project/
workspace.rs1#[cfg(all(feature = "std", feature = "serde"))]
2use std::string::{String, ToString};
3#[cfg(feature = "std")]
4use std::{boxed::Box, path::Path};
5
6#[cfg(all(feature = "std", feature = "serde"))]
7use miden_assembly_syntax::debuginfo::SourceManager;
8
9use crate::*;
10
11#[derive(Debug)]
16pub struct Workspace {
17 #[cfg(feature = "std")]
19 manifest_path: Option<Box<Path>>,
20 members: Vec<Arc<Package>>,
22}
23
24impl Workspace {
26 #[cfg(feature = "std")]
28 pub fn manifest_path(&self) -> Option<&Path> {
29 self.manifest_path.as_deref()
30 }
31
32 #[cfg(feature = "std")]
34 pub fn workspace_root(&self) -> Option<&Path> {
35 self.manifest_path()?.parent()
36 }
37
38 pub fn members(&self) -> &[Arc<Package>] {
40 &self.members
41 }
42
43 pub fn get_member_by_name(&self, name: impl AsRef<str>) -> Option<Arc<Package>> {
45 let name = name.as_ref();
46 self.members().iter().find(|member| &**member.name().inner() == name).cloned()
47 }
48
49 #[cfg(feature = "std")]
51 pub fn get_member_by_relative_path(&self, path: impl AsRef<Path>) -> Option<Arc<Package>> {
52 let path = path.as_ref();
53 let path = self.workspace_root()?.join(path);
54 self.members()
55 .iter()
56 .find(|member| {
57 member.manifest_path().is_some_and(|p| p.parent() == Some(path.as_path()))
58 })
59 .cloned()
60 }
61}
62
63#[cfg(all(feature = "std", feature = "serde"))]
65impl Workspace {
66 pub fn load(
69 source: Arc<SourceFile>,
70 source_manager: &dyn SourceManager,
71 ) -> Result<Box<Self>, Report> {
72 use miden_assembly_syntax::debuginfo::SourceManagerExt;
73
74 use crate::ast::ProjectFileError;
75
76 let file = ast::WorkspaceFile::parse(source.clone())?;
77
78 let manifest_uri = source.content().uri();
79 let manifest_path = if manifest_uri.scheme().is_none_or(|scheme| scheme == "file") {
80 Some(Path::new(manifest_uri.path()).to_path_buf().into_boxed_path())
81 } else {
82 None
83 };
84
85 let members = file.workspace.members.clone();
86
87 let mut workspace = Box::new(Workspace {
88 manifest_path,
89 members: Vec::with_capacity(members.len()),
90 });
91 let mut seen_member_names = Map::<String, SourceSpan>::default();
92
93 for member in members {
94 let Some(workspace_root) = workspace.workspace_root() else {
95 return Err(ProjectFileError::LoadWorkspaceMemberFailed {
96 source_file: source.clone(),
97 span: Label::new(
98 member.span(),
99 "cannot load workspace members for virtual workspace manifest: manifest path must be resolvable",
100 ),
101 }
102 .into());
103 };
104 let relative_path = Path::new(member.as_str());
105 let member_dir = absolutize_path(relative_path, workspace_root).map_err(|err| {
106 ProjectFileError::LoadWorkspaceMemberFailed {
107 source_file: source.clone(),
108 span: Label::new(member.span(), err.to_string()),
109 }
110 })?;
111 if member_dir.strip_prefix(workspace_root).is_err() {
112 return Err(ProjectFileError::LoadWorkspaceMemberFailed {
113 source_file: source.clone(),
114 span: Label::new(
115 member.span(),
116 "workspace members must be located within the workspace root",
117 ),
118 }
119 .into());
120 }
121 let manifest_path = member_dir.join("miden-project.toml");
122 let member_manifest = source_manager.load_file(&manifest_path).map_err(|err| {
123 ProjectFileError::LoadWorkspaceMemberFailed {
124 source_file: source.clone(),
125 span: Label::new(member.span(), err.to_string()),
126 }
127 })?;
128 let package = Package::load_from_workspace(member_manifest, &file)?;
129 let package_name = package.name().inner().to_string();
130 if let Some(prev) = seen_member_names.insert(package_name.clone(), member.span()) {
131 return Err(ProjectFileError::DuplicateWorkspaceMember {
132 name: package_name,
133 source_file: source.clone(),
134 span: member.span(),
135 prev,
136 }
137 .into());
138 }
139 workspace.members.push(Arc::from(package));
140 }
141
142 Ok(workspace)
143 }
144}