1use alloc::boxed::Box;
2#[cfg(feature = "std")]
3use std::path::Path;
4
5#[cfg(feature = "std")]
6use miden_assembly_syntax::debuginfo::Spanned;
7use miden_mast_package::PackageId;
8
9#[cfg(feature = "std")]
10use crate::ast::{ProjectFileError, WorkspaceFile};
11use crate::*;
12
13#[derive(Debug)]
15pub struct Package {
16 #[cfg(feature = "std")]
18 manifest_path: Option<Box<Path>>,
19 name: Span<PackageId>,
21 version: Span<SemVer>,
23 description: Option<Arc<str>>,
25 dependencies: Vec<Dependency>,
27 lints: MetadataSet,
31 metadata: MetadataSet,
35 lib: Option<Span<Target>>,
37 bins: Vec<Span<Target>>,
39 profiles: Vec<Profile>,
41}
42
43impl Package {
45 pub fn new(name: impl Into<PackageId>, default_target: Target) -> Box<Self> {
51 let name = name.into();
52 let (lib, bins) = if default_target.is_library() {
53 (Some(Span::unknown(default_target)), vec![])
54 } else {
55 (None, vec![Span::unknown(default_target)])
56 };
57 let profiles = vec![Profile::default(), Profile::release()];
58 Box::new(Self {
59 #[cfg(feature = "std")]
60 manifest_path: None,
61 name: Span::unknown(name),
62 version: Span::unknown(SemVer::new(0, 0, 0)),
63 description: None,
64 dependencies: Default::default(),
65 lints: Default::default(),
66 metadata: Default::default(),
67 lib,
68 bins,
69 profiles,
70 })
71 }
72
73 pub fn with_version(mut self: Box<Self>, version: SemVer) -> Box<Self> {
75 *self.version = version;
76 self
77 }
78
79 pub fn with_lints(mut self: Box<Self>, lints: MetadataSet) -> Box<Self> {
81 self.lints = lints;
82 self
83 }
84
85 pub fn with_metadata(mut self: Box<Self>, metadata: MetadataSet) -> Box<Self> {
87 self.metadata = metadata;
88 self
89 }
90
91 pub fn with_targets(
96 mut self: Box<Self>,
97 targets: impl IntoIterator<Item = Target>,
98 ) -> Box<Self> {
99 for target in targets {
100 if target.is_library() {
101 assert!(self.lib.is_none(), "a package cannot have duplicate library targets");
102 self.lib = Some(Span::unknown(target));
103 } else {
104 if self.bins.iter().any(|t| t.name == target.name) {
105 panic!("duplicate definitions of the same target '{}'", &target.name);
106 }
107 self.bins.push(Span::unknown(target));
108 }
109 }
110 self
111 }
112
113 pub fn with_profile(mut self: Box<Self>, profile: Profile) -> Box<Self> {
117 for existing in self.profiles.iter_mut() {
118 if existing.name() == profile.name() {
119 existing.merge(&profile);
120 return self;
121 }
122 }
123
124 self.profiles.push(profile);
125 self
126 }
127
128 pub fn with_dependencies(
133 mut self: Box<Self>,
134 dependencies: impl IntoIterator<Item = Dependency>,
135 ) -> Box<Self> {
136 for dependency in dependencies {
137 if self.dependencies().iter().any(|dep| dep.name() == dependency.name()) {
138 panic!("duplicate definitions of dependency '{}'", dependency.name());
139 }
140 self.dependencies.push(dependency);
141 }
142
143 self
144 }
145}
146
147impl Package {
149 pub fn name(&self) -> Span<PackageId> {
151 self.name.clone()
152 }
153
154 pub fn version(&self) -> Span<&SemVer> {
156 self.version.as_ref()
157 }
158
159 pub fn description(&self) -> Option<Arc<str>> {
161 self.description.clone()
162 }
163
164 pub fn set_description(&mut self, description: impl Into<Arc<str>>) {
166 self.description = Some(description.into());
167 }
168
169 pub fn dependencies(&self) -> &[Dependency] {
171 &self.dependencies
172 }
173
174 pub fn num_dependencies(&self) -> usize {
176 self.dependencies.len()
177 }
178
179 pub fn lints(&self) -> &MetadataSet {
181 &self.lints
182 }
183
184 pub fn metadata(&self) -> &MetadataSet {
186 &self.metadata
187 }
188
189 pub fn profiles(&self) -> &[Profile] {
191 &self.profiles
192 }
193
194 pub fn get_profile(&self, name: &str) -> Option<&Profile> {
197 self.profiles().iter().find(|profile| profile.name().as_ref() == name)
198 }
199
200 pub fn library_target(&self) -> Option<&Span<Target>> {
202 self.lib.as_ref()
203 }
204
205 pub fn executable_targets(&self) -> &[Span<Target>] {
207 &self.bins
208 }
209
210 #[cfg(feature = "std")]
212 pub fn manifest_path(&self) -> Option<&Path> {
213 self.manifest_path.as_deref()
214 }
215}
216
217#[cfg(all(feature = "std", feature = "serde"))]
219impl Package {
220 pub fn load(source: Arc<SourceFile>) -> Result<Box<Self>, Report> {
223 Self::parse(source, None)
224 }
225
226 pub fn load_from_workspace(
229 source: Arc<SourceFile>,
230 workspace: &WorkspaceFile,
231 ) -> Result<Box<Self>, Report> {
232 Self::parse(source, Some(workspace))
233 }
234
235 fn parse(
236 source: Arc<SourceFile>,
237 workspace: Option<&WorkspaceFile>,
238 ) -> Result<Box<Self>, Report> {
239 let manifest_path = Path::new(source.uri().path());
240 let manifest_path = if manifest_path.try_exists().is_ok_and(|exists| exists) {
241 Some(manifest_path.to_path_buf().into_boxed_path())
242 } else {
243 None
244 };
245
246 let package_ast = ast::ProjectFile::parse(source.clone())?;
248
249 let version = package_ast.get_or_inherit_version(source.clone(), workspace)?;
251 let description = package_ast.get_or_inherit_description(source.clone(), workspace)?;
252
253 let mut profiles = Vec::default();
255 profiles.push(Profile::default());
256 profiles.push(Profile::release());
257 if let Some(workspace) = workspace {
258 for ast in workspace.profiles.iter() {
259 let profile = Profile::from_ast(ast, source.clone(), &profiles)?;
260 if let Some(prev) = profiles.iter_mut().find(|p| p.name() == ast.name.inner()) {
261 *prev = profile;
262 } else {
263 profiles.push(profile);
264 }
265 }
266 }
267
268 let package_profiles_start = profiles.len();
272 for ast in package_ast.profiles.iter() {
273 let profile = Profile::from_ast(ast, source.clone(), &profiles)?;
274
275 if let Some(prev_index) = profiles.iter().position(|p| p.name() == profile.name()) {
276 if prev_index < package_profiles_start {
277 profiles[prev_index].merge(&profile);
278 } else {
279 let prev = &profiles[prev_index];
280 return Err(ProjectFileError::DuplicateProfile {
281 name: prev.name().clone(),
282 source_file: source,
283 span: profile.span(),
284 prev: prev.span(),
285 }
286 .into());
287 }
288 } else {
289 profiles.push(profile);
290 }
291 }
292
293 let dependencies = package_ast.extract_dependencies(source.clone(), workspace)?;
296
297 let lib = package_ast.extract_library_target()?;
299 let bins = package_ast.extract_executable_targets();
300
301 let mut lints = workspace.map(|ws| ws.workspace.config.lints.clone()).unwrap_or_default();
302 lints.extend(package_ast.config.lints.clone());
303
304 let mut metadata =
305 workspace.map(|ws| ws.workspace.package.metadata.clone()).unwrap_or_default();
306 metadata.extend(package_ast.package.detail.metadata.clone());
307
308 Ok(Box::new(Self {
309 manifest_path,
310 name: package_ast.package.name.clone().map(|id| id.into()),
311 version,
312 description,
313 dependencies,
314 lints,
315 metadata,
316 profiles,
317 lib,
318 bins,
319 }))
320 }
321}
322
323#[cfg(feature = "serde")]
324impl Package {
325 pub fn to_toml(&self) -> Result<alloc::string::String, Report> {
331 let manifest_ast = ast::ProjectFile {
332 source_file: None,
333 package: ast::PackageTable {
334 name: self.name().map(|id| id.into_inner()),
335 detail: ast::PackageDetail {
336 version: Some(
337 self.version().map(|v| ast::parsing::MaybeInherit::Value(v.clone())),
338 ),
339 description: self
340 .description()
341 .map(ast::parsing::MaybeInherit::Value)
342 .map(Span::unknown),
343 metadata: self.metadata.clone(),
344 },
345 },
346 config: ast::PackageConfig {
347 dependencies: self
348 .dependencies()
349 .iter()
350 .map(|dep| {
351 let name = Span::unknown(dep.name().clone());
352 let linkage = if matches!(dep.linkage(), Linkage::Dynamic) {
353 None
354 } else {
355 Some(Span::unknown(dep.linkage()))
356 };
357 let spec = match dep.scheme() {
358 DependencyVersionScheme::Workspace { .. } => ast::DependencySpec {
359 name: name.clone(),
360 version_or_digest: None,
361 workspace: true,
362 path: None,
363 git: None,
364 branch: None,
365 rev: None,
366 linkage,
367 },
368 DependencyVersionScheme::WorkspacePath { path, version } => {
369 ast::DependencySpec {
370 name: name.clone(),
371 version_or_digest: version.clone(),
372 workspace: false,
373 path: Some(path.clone()),
374 git: None,
375 branch: None,
376 rev: None,
377 linkage,
378 }
379 },
380 DependencyVersionScheme::Registry(req) => ast::DependencySpec {
381 name: name.clone(),
382 version_or_digest: Some(req.clone()),
383 workspace: false,
384 path: None,
385 git: None,
386 branch: None,
387 rev: None,
388 linkage,
389 },
390 DependencyVersionScheme::Path { path, version } => {
391 ast::DependencySpec {
392 name: name.clone(),
393 version_or_digest: version.clone(),
394 workspace: false,
395 path: Some(path.clone()),
396 git: None,
397 branch: None,
398 rev: None,
399 linkage,
400 }
401 },
402 DependencyVersionScheme::Git { repo, revision, version } => {
403 let (branch, rev) = match revision.inner() {
404 GitRevision::Branch(b) => {
405 (Some(Span::new(revision.span(), b.clone())), None)
406 },
407 GitRevision::Commit(c) => {
408 (None, Some(Span::new(revision.span(), c.clone())))
409 },
410 };
411 ast::DependencySpec {
412 name: name.clone(),
413 version_or_digest: version.as_ref().map(|spanned| {
414 VersionRequirement::from(spanned.inner().clone())
415 }),
416 workspace: false,
417 path: None,
418 git: Some(repo.clone()),
419 branch,
420 rev,
421 linkage,
422 }
423 },
424 };
425
426 (name, Span::unknown(spec))
427 })
428 .collect(),
429 lints: self.lints.clone(),
430 },
431 lib: self.lib.as_ref().map(|lib| {
432 Span::unknown(ast::LibTarget {
433 kind: if matches!(lib.ty, TargetType::Library) {
434 None
435 } else {
436 Some(Span::unknown(lib.ty))
437 },
438 namespace: Some(lib.namespace.as_ref().map(|path| path.as_str().into())),
439 path: lib.path.clone(),
440 })
441 }),
442 bins: self
443 .bins
444 .iter()
445 .map(|bin| {
446 Span::unknown(ast::BinTarget {
447 name: Some(bin.name.clone()),
448 path: bin.path.clone(),
449 })
450 })
451 .collect(),
452 profiles: self
453 .profiles()
454 .iter()
455 .map(|profile| ast::Profile {
456 inherits: None,
457 name: Span::unknown(profile.name().clone()),
458 debug: Some(profile.should_emit_debug_info()),
459 trim_paths: Some(profile.should_trim_paths()),
460 metadata: profile.metadata().clone(),
461 })
462 .collect(),
463 };
464
465 toml::to_string_pretty(&manifest_ast)
466 .map_err(|err| Report::msg(format!("failed to pretty print project manifest: {err}")))
467 }
468}