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};
28pub 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
45pub type Map<K, V> = alloc::collections::BTreeMap<K, V>;
47
48pub type Metadata = Map<Span<Arc<str>>, Span<Value>>;
52
53pub type MetadataSet = Map<Span<Arc<str>>, Metadata>;
57
58#[derive(Debug, Clone)]
60pub enum Project {
61 WorkspacePackage {
63 package: Arc<Package>,
65 workspace: Arc<Workspace>,
67 },
68 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 pub fn is_workspace_member(&self) -> bool {
87 matches!(self, Self::WorkspacePackage { .. })
88 }
89
90 pub fn package(&self) -> Arc<Package> {
92 match self {
93 Self::WorkspacePackage { package, .. } | Self::Package(package) => Arc::clone(package),
94 }
95 }
96
97 #[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#[cfg(all(feature = "std", feature = "serde"))]
110impl Project {
111 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 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#[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}