Skip to main content

miden_project/
package.rs

1#[cfg(feature = "std")]
2use std::{boxed::Box, path::Path};
3
4#[cfg(feature = "std")]
5use miden_assembly_syntax::debuginfo::Spanned;
6
7#[cfg(feature = "std")]
8use crate::ast::{ProjectFileError, WorkspaceFile};
9use crate::*;
10
11/// The representation of an individual package in a Miden project
12#[derive(Debug)]
13pub struct Package {
14    /// The file path of the manifest corresponding to this package metadata, if applicable.
15    #[cfg(feature = "std")]
16    manifest_path: Option<Box<Path>>,
17    /// The name of the package
18    name: Span<Arc<str>>,
19    /// The semantic version associated with the package
20    version: Span<SemVer>,
21    /// The optional package description
22    description: Option<Arc<str>>,
23    /// The set of dependencies required by this package
24    dependencies: Vec<Dependency>,
25    /// The lint configuration specific to this package.
26    ///
27    /// By default, this is empty.
28    lints: MetadataSet,
29    /// The set of custom metadata attached to this package.
30    ///
31    /// By default, this is empty.
32    metadata: MetadataSet,
33    /// The library target for this package, if specified.
34    lib: Option<Span<Target>>,
35    /// The executable targets available for this package.
36    bins: Vec<Span<Target>>,
37    /// The build profiles configured for this package.
38    profiles: Vec<Profile>,
39}
40
41/// Accessors
42impl Package {
43    /// Get the name of this package
44    pub fn name(&self) -> Span<Arc<str>> {
45        self.name.clone()
46    }
47
48    /// Get the semantic version of this package
49    pub fn version(&self) -> Span<&SemVer> {
50        self.version.as_ref()
51    }
52
53    /// Get the description of this package, if specified
54    pub fn description(&self) -> Option<Arc<str>> {
55        self.description.clone()
56    }
57
58    /// Get the set of dependencies this package requires
59    pub fn dependencies(&self) -> &[Dependency] {
60        &self.dependencies
61    }
62
63    /// Get the number of dependencies this package requires
64    pub fn num_dependencies(&self) -> usize {
65        self.dependencies.len()
66    }
67
68    /// Get a reference to the linter metadata configured for this package
69    pub fn lints(&self) -> &MetadataSet {
70        &self.lints
71    }
72
73    /// Get a reference to the custom metadata configured for this package
74    pub fn metadata(&self) -> &MetadataSet {
75        &self.metadata
76    }
77
78    /// Get a reference to the build profiles configured for this package
79    pub fn profiles(&self) -> &[Profile] {
80        &self.profiles
81    }
82
83    /// Get a reference to the library build target provided by this package
84    pub fn library_target(&self) -> Option<&Span<Target>> {
85        self.lib.as_ref()
86    }
87
88    /// Get a reference to the executable build targets provided by this package
89    pub fn executable_targets(&self) -> &[Span<Target>] {
90        &self.bins
91    }
92
93    /// Get the location of the manifest this package was loaded from, if known/applicable.
94    #[cfg(feature = "std")]
95    pub fn manifest_path(&self) -> Option<&Path> {
96        self.manifest_path.as_deref()
97    }
98}
99
100/// Parsing
101#[cfg(all(feature = "std", feature = "serde"))]
102impl Package {
103    /// Load a package from `source`, expected to be a standalone package-level `miden-project.toml`
104    /// manifest.
105    pub fn load(source: Arc<SourceFile>) -> Result<Box<Self>, Report> {
106        Self::parse(source, None)
107    }
108
109    /// Load a package from `source`, expected to be a package-level `miden-project.toml` manifest
110    /// which is presumed to be a member of `workspace` for purposes of configuration inheritance.
111    pub fn load_from_workspace(
112        source: Arc<SourceFile>,
113        workspace: &WorkspaceFile,
114    ) -> Result<Box<Self>, Report> {
115        Self::parse(source, Some(workspace))
116    }
117
118    fn parse(
119        source: Arc<SourceFile>,
120        workspace: Option<&WorkspaceFile>,
121    ) -> Result<Box<Self>, Report> {
122        let manifest_path = Path::new(source.uri().path());
123        let manifest_path = if manifest_path.try_exists().is_ok_and(|exists| exists) {
124            Some(manifest_path.to_path_buf().into_boxed_path())
125        } else {
126            None
127        };
128
129        // Parse the manifest into an AST for further processing
130        let package_ast = ast::ProjectFile::parse(source.clone())?;
131
132        // Extract metadata that can be inherited from the workspace manifest (if present)
133        let version = package_ast.get_or_inherit_version(source.clone(), workspace)?;
134        let description = package_ast.get_or_inherit_description(source.clone(), workspace)?;
135
136        // Compute the set of initial profiles inheritable from the workspace level
137        let mut profiles = Vec::default();
138        profiles.push(Profile::default());
139        profiles.push(Profile::release());
140        if let Some(workspace) = workspace {
141            for ast in workspace.profiles.iter() {
142                profiles.push(Profile::from_ast(ast, source.clone(), &profiles)?);
143            }
144        }
145
146        // Compute the effective profiles for this project, merging over the top of workspace-level
147        // profiles, but raising an error if the same profile is mentioned twice in the current
148        // project file.
149        let package_profiles_start = profiles.len();
150        for ast in package_ast.profiles.iter() {
151            let profile = Profile::from_ast(ast, source.clone(), &profiles)?;
152
153            if let Some(prev_index) = profiles.iter().position(|p| p.name() == profile.name()) {
154                if prev_index < package_profiles_start {
155                    profiles[prev_index].merge(&profile);
156                } else {
157                    let prev = &profiles[prev_index];
158                    return Err(ProjectFileError::DuplicateProfile {
159                        name: prev.name().clone(),
160                        source_file: source,
161                        span: profile.span(),
162                        prev: prev.span(),
163                    }
164                    .into());
165                }
166            } else {
167                profiles.push(profile);
168            }
169        }
170
171        // Extract project dependencies, using the workspace to resolve workspace-relative
172        // dependencies
173        let dependencies = package_ast.extract_dependencies(source.clone(), workspace)?;
174
175        // Extract the build targets for this project
176        let lib = package_ast.extract_library_target()?;
177        let bins = package_ast.extract_executable_targets();
178
179        let mut lints = workspace.map(|ws| ws.workspace.config.lints.clone()).unwrap_or_default();
180        lints.extend(package_ast.config.lints.clone());
181
182        let mut metadata =
183            workspace.map(|ws| ws.workspace.package.metadata.clone()).unwrap_or_default();
184        metadata.extend(package_ast.package.detail.metadata.clone());
185
186        Ok(Box::new(Self {
187            manifest_path,
188            name: package_ast.package.name.clone(),
189            version,
190            description,
191            dependencies,
192            lints,
193            metadata,
194            profiles,
195            lib,
196            bins,
197        }))
198    }
199}