1use alloc::{
2 boxed::Box,
3 string::{String, ToString},
4};
5#[cfg(feature = "std")]
6use std::path::Path;
7
8#[cfg(all(feature = "std", feature = "serde"))]
9use miden_assembly_syntax::debuginfo::Spanned;
10use miden_mast_package::PackageId;
11
12#[cfg(all(feature = "std", feature = "serde"))]
13use crate::ast::{ProjectFileError, WorkspaceFile};
14use crate::*;
15
16#[derive(Debug)]
18pub struct Package {
19 #[cfg(feature = "std")]
21 manifest_path: Option<Box<Path>>,
22 name: Span<PackageId>,
24 version: Span<SemVer>,
26 description: Option<Arc<str>>,
28 dependencies: Vec<Dependency>,
30 lints: MetadataSet,
34 metadata: MetadataSet,
38 lib: Option<Span<Target>>,
40 bins: Vec<Span<Target>>,
42 profiles: Vec<Profile>,
44}
45
46impl Package {
48 pub fn new(name: impl Into<PackageId>, default_target: Target) -> Box<Self> {
54 let name = name.into();
55 let (lib, bins) = if default_target.is_library() {
56 (Some(Span::unknown(default_target)), vec![])
57 } else {
58 (None, vec![Span::unknown(default_target)])
59 };
60 let profiles = vec![Profile::default(), Profile::release()];
61 Box::new(Self {
62 #[cfg(feature = "std")]
63 manifest_path: None,
64 name: Span::unknown(name),
65 version: Span::unknown(SemVer::new(0, 0, 0)),
66 description: None,
67 dependencies: Default::default(),
68 lints: Default::default(),
69 metadata: Default::default(),
70 lib,
71 bins,
72 profiles,
73 })
74 }
75
76 pub fn with_version(mut self: Box<Self>, version: SemVer) -> Box<Self> {
78 *self.version = version;
79 self
80 }
81
82 pub fn with_lints(mut self: Box<Self>, lints: MetadataSet) -> Box<Self> {
84 self.lints = lints;
85 self
86 }
87
88 pub fn with_metadata(mut self: Box<Self>, metadata: MetadataSet) -> Box<Self> {
90 self.metadata = metadata;
91 self
92 }
93
94 pub fn with_targets(
99 mut self: Box<Self>,
100 targets: impl IntoIterator<Item = Target>,
101 ) -> Box<Self> {
102 for target in targets {
103 if target.is_library() {
104 assert!(self.lib.is_none(), "a package cannot have duplicate library targets");
105 self.lib = Some(Span::unknown(target));
106 } else {
107 if self.bins.iter().any(|t| t.name == target.name) {
108 panic!("duplicate definitions of the same target '{}'", target.name);
109 }
110 self.bins.push(Span::unknown(target));
111 }
112 }
113 self
114 }
115
116 pub fn with_profile(mut self: Box<Self>, profile: Profile) -> Box<Self> {
120 for existing in self.profiles.iter_mut() {
121 if existing.name() == profile.name() {
122 existing.merge(&profile);
123 return self;
124 }
125 }
126
127 self.profiles.push(profile);
128 self
129 }
130
131 pub fn with_dependencies(
136 mut self: Box<Self>,
137 dependencies: impl IntoIterator<Item = Dependency>,
138 ) -> Box<Self> {
139 for dependency in dependencies {
140 if self.dependencies().iter().any(|dep| dep.name() == dependency.name()) {
141 panic!("duplicate definitions of dependency '{}'", dependency.name());
142 }
143 self.dependencies.push(dependency);
144 }
145
146 self
147 }
148}
149
150impl Package {
152 pub fn name(&self) -> Span<PackageId> {
154 self.name.clone()
155 }
156
157 pub fn version(&self) -> Span<&SemVer> {
159 self.version.as_ref()
160 }
161
162 pub fn description(&self) -> Option<Arc<str>> {
164 self.description.clone()
165 }
166
167 pub fn set_description(&mut self, description: impl Into<Arc<str>>) {
169 self.description = Some(description.into());
170 }
171
172 pub fn dependencies(&self) -> &[Dependency] {
174 &self.dependencies
175 }
176
177 pub fn num_dependencies(&self) -> usize {
179 self.dependencies.len()
180 }
181
182 pub fn lints(&self) -> &MetadataSet {
184 &self.lints
185 }
186
187 pub fn metadata(&self) -> &MetadataSet {
189 &self.metadata
190 }
191
192 pub fn profiles(&self) -> &[Profile] {
194 &self.profiles
195 }
196
197 pub fn get_profile(&self, name: &str) -> Option<&Profile> {
200 self.profiles().iter().find(|profile| profile.name().as_ref() == name)
201 }
202
203 pub fn library_target(&self) -> Option<&Span<Target>> {
205 self.lib.as_ref()
206 }
207
208 pub fn executable_targets(&self) -> &[Span<Target>] {
210 &self.bins
211 }
212
213 #[cfg(feature = "std")]
215 pub fn manifest_path(&self) -> Option<&Path> {
216 self.manifest_path.as_deref()
217 }
218
219 pub fn build_provenance_projection(&self, target: &Target, profile: &Profile) -> String {
222 let Self {
223 #[cfg(feature = "std")]
224 manifest_path: _,
225 name,
226 version,
227 description: _,
228 dependencies: _,
229 lints: _,
230 metadata: _,
231 lib: _,
232 bins: _,
233 profiles: _,
234 } = self;
235
236 let mut projection = String::new();
237 projection.push_str("package:name:");
238 projection.push_str(name.inner().as_ref());
239 projection.push('\n');
240 projection.push_str("package:version:");
241 projection.push_str(version.inner().to_string().as_str());
242 projection.push('\n');
243 target.append_build_provenance_projection(&mut projection);
244 profile.append_build_provenance_projection(&mut projection);
245 projection
246 }
247}
248
249#[cfg(all(feature = "std", feature = "serde"))]
251impl Package {
252 pub fn load(source: Arc<SourceFile>) -> Result<Box<Self>, Report> {
255 Self::parse(source, None)
256 }
257
258 pub fn load_from_workspace(
261 source: Arc<SourceFile>,
262 workspace: &WorkspaceFile,
263 ) -> Result<Box<Self>, Report> {
264 Self::parse(source, Some(workspace))
265 }
266
267 fn parse(
268 source: Arc<SourceFile>,
269 workspace: Option<&WorkspaceFile>,
270 ) -> Result<Box<Self>, Report> {
271 let manifest_path = Path::new(source.uri().path());
272 let manifest_path = if manifest_path.try_exists().is_ok_and(|exists| exists) {
273 Some(manifest_path.to_path_buf().into_boxed_path())
274 } else {
275 None
276 };
277
278 let package_ast = ast::ProjectFile::parse(source.clone())?;
280
281 let version = package_ast.get_or_inherit_version(source.clone(), workspace)?;
283 let description = package_ast.get_or_inherit_description(source.clone(), workspace)?;
284
285 let mut profiles = Vec::default();
287 profiles.push(Profile::default());
288 profiles.push(Profile::release());
289 if let Some(workspace) = workspace {
290 for ast in workspace.profiles.iter() {
291 let profile = Profile::from_ast(ast, source.clone(), &profiles)?;
292 if let Some(prev) = profiles.iter_mut().find(|p| p.name() == ast.name.inner()) {
293 *prev = profile;
294 } else {
295 profiles.push(profile);
296 }
297 }
298 }
299
300 let package_profiles_start = profiles.len();
304 for ast in package_ast.profiles.iter() {
305 let profile = Profile::from_ast(ast, source.clone(), &profiles)?;
306
307 if let Some(prev_index) = profiles.iter().position(|p| p.name() == profile.name()) {
308 if prev_index < package_profiles_start {
309 profiles[prev_index].merge(&profile);
310 } else {
311 let prev = &profiles[prev_index];
312 return Err(ProjectFileError::DuplicateProfile {
313 name: prev.name().clone(),
314 source_file: source,
315 span: profile.span(),
316 prev: prev.span(),
317 }
318 .into());
319 }
320 } else {
321 profiles.push(profile);
322 }
323 }
324
325 let dependencies = package_ast.extract_dependencies(source.clone(), workspace)?;
328
329 let lib = package_ast.extract_library_target()?;
331 let bins = package_ast.extract_executable_targets();
332
333 let mut lints = workspace.map(|ws| ws.workspace.config.lints.clone()).unwrap_or_default();
334 lints.extend(package_ast.config.lints.clone());
335
336 let mut metadata =
337 workspace.map(|ws| ws.workspace.package.metadata.clone()).unwrap_or_default();
338 metadata.extend(package_ast.package.detail.metadata.clone());
339
340 Ok(Box::new(Self {
341 manifest_path,
342 name: package_ast.package.name.map(Into::into),
343 version,
344 description,
345 dependencies,
346 lints,
347 metadata,
348 profiles,
349 lib,
350 bins,
351 }))
352 }
353}
354
355#[cfg(feature = "serde")]
356impl Package {
357 pub fn to_toml(&self) -> Result<String, Report> {
363 let manifest_ast = ast::ProjectFile {
364 source_file: None,
365 package: ast::PackageTable {
366 name: self.name().map(PackageId::into_inner),
367 detail: ast::PackageDetail {
368 version: Some(
369 self.version().map(|v| ast::parsing::MaybeInherit::Value(v.clone())),
370 ),
371 description: self
372 .description()
373 .map(ast::parsing::MaybeInherit::Value)
374 .map(Span::unknown),
375 metadata: self.metadata.clone(),
376 },
377 },
378 config: ast::PackageConfig {
379 dependencies: self
380 .dependencies()
381 .iter()
382 .map(|dep| {
383 let name = Span::unknown(dep.name().clone());
384 let linkage = if matches!(dep.linkage(), Linkage::Dynamic) {
385 None
386 } else {
387 Some(Span::unknown(dep.linkage()))
388 };
389 let spec = match dep.scheme() {
390 DependencyVersionScheme::Workspace { .. } => ast::DependencySpec {
391 name: name.clone(),
392 version_or_digest: None,
393 workspace: true,
394 path: None,
395 git: None,
396 branch: None,
397 rev: None,
398 linkage,
399 },
400 DependencyVersionScheme::WorkspacePath { path, version } => {
401 ast::DependencySpec {
402 name: name.clone(),
403 version_or_digest: version.clone(),
404 workspace: false,
405 path: Some(path.clone()),
406 git: None,
407 branch: None,
408 rev: None,
409 linkage,
410 }
411 },
412 DependencyVersionScheme::Registry(req) => ast::DependencySpec {
413 name: name.clone(),
414 version_or_digest: Some(req.clone()),
415 workspace: false,
416 path: None,
417 git: None,
418 branch: None,
419 rev: None,
420 linkage,
421 },
422 DependencyVersionScheme::Path { path, version } => {
423 ast::DependencySpec {
424 name: name.clone(),
425 version_or_digest: version.clone(),
426 workspace: false,
427 path: Some(path.clone()),
428 git: None,
429 branch: None,
430 rev: None,
431 linkage,
432 }
433 },
434 DependencyVersionScheme::Git { repo, revision, version } => {
435 let (branch, rev) = match revision.inner() {
436 GitRevision::Branch(b) => {
437 (Some(Span::new(revision.span(), b.clone())), None)
438 },
439 GitRevision::Commit(c) => {
440 (None, Some(Span::new(revision.span(), c.clone())))
441 },
442 };
443 ast::DependencySpec {
444 name: name.clone(),
445 version_or_digest: version.as_ref().map(|spanned| {
446 VersionRequirement::from(spanned.inner().clone())
447 }),
448 workspace: false,
449 path: None,
450 git: Some(repo.clone()),
451 branch,
452 rev,
453 linkage,
454 }
455 },
456 };
457
458 (name, Span::unknown(spec))
459 })
460 .collect(),
461 lints: self.lints.clone(),
462 },
463 lib: self.lib.as_ref().map(|lib| {
464 Span::unknown(ast::LibTarget {
465 kind: if matches!(lib.ty, TargetType::Library) {
466 None
467 } else {
468 Some(Span::unknown(lib.ty))
469 },
470 namespace: Some(lib.namespace.as_ref().map(|path| path.as_str().into())),
471 path: lib.path.clone(),
472 })
473 }),
474 bins: self
475 .bins
476 .iter()
477 .map(|bin| {
478 Span::unknown(ast::BinTarget {
479 name: Some(bin.name.clone()),
480 path: bin.path.clone(),
481 })
482 })
483 .collect(),
484 profiles: self
485 .profiles()
486 .iter()
487 .map(|profile| ast::Profile {
488 inherits: None,
489 name: Span::unknown(profile.name().clone()),
490 debug: Some(profile.should_emit_debug_info()),
491 trim_paths: Some(profile.should_trim_paths()),
492 metadata: profile.metadata().clone(),
493 })
494 .collect(),
495 };
496
497 toml::to_string_pretty(&manifest_ast)
498 .map_err(|err| Report::msg(format!("failed to pretty print project manifest: {err}")))
499 }
500}