scarb_metadata/
lib.rs

1#![deny(missing_docs)]
2#![warn(rustdoc::broken_intra_doc_links)]
3#![deny(rustdoc::private_intra_doc_links)]
4#![warn(rust_2018_idioms)]
5#![doc = concat!(
6    "Structured access to the output of `scarb metadata --format-version ",
7    env!("CARGO_PKG_VERSION_MAJOR"),
8    "`.
9")]
10//! Usually used by Scarb extensions and other developer tools.
11//!
12//! [Scarb](https://docs.swmansion.com/scarb) is a build toolchain and package manager for
13//! the [Cairo language](https://www.cairo-lang.org/).
14//! See the [Scarb documentation](https://docs.swmansion.com/scarb/docs.html) for details on
15//! Scarb itself.
16//!
17//! With the `command` feature (enabled by default), this crate also exposes an ergonomic interface
18//! to collect metadata from Scarb: [`MetadataCommand`].
19
20use std::collections::{BTreeMap, HashMap};
21use std::fmt;
22use std::ops::Index;
23use std::path::PathBuf;
24
25use camino::{Utf8Path, Utf8PathBuf};
26#[cfg(feature = "builder")]
27use derive_builder::Builder;
28use semver::{Version, VersionReq};
29use serde::{Deserialize, Serialize};
30
31#[cfg(feature = "command")]
32pub use command::*;
33pub use version_pin::*;
34
35#[cfg(feature = "command")]
36mod command;
37mod version_pin;
38
39/// An "opaque" identifier for a package.
40/// It is possible to inspect the `repr` field if the need arises,
41/// but its precise format is an implementation detail and is subject to change.
42///
43/// [`Metadata`] can be indexed by [`PackageId`].
44#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
45#[serde(transparent)]
46pub struct PackageId {
47    /// The underlying string representation of the ID.
48    pub repr: String,
49}
50
51impl From<String> for PackageId {
52    fn from(repr: String) -> Self {
53        Self { repr }
54    }
55}
56
57impl fmt::Display for PackageId {
58    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
59        fmt::Display::fmt(&self.repr, f)
60    }
61}
62
63/// An "opaque" identifier for a source.
64/// It is possible to inspect the `repr` field if the need arises,
65/// but its precise format is an implementation detail and is subject to change.
66#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
67#[serde(transparent)]
68pub struct SourceId {
69    /// The underlying string representation of the ID.
70    pub repr: String,
71}
72
73impl From<String> for SourceId {
74    fn from(repr: String) -> Self {
75        Self { repr }
76    }
77}
78
79impl fmt::Display for SourceId {
80    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
81        fmt::Display::fmt(&self.repr, f)
82    }
83}
84
85/// An "opaque" identifier for a compilation unit.
86/// It is possible to inspect the `repr` field if the need arises,
87/// but its precise format is an implementation detail and is subject to change.
88///
89/// [`Metadata`] can be indexed by [`CompilationUnitId`].
90#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
91#[serde(transparent)]
92pub struct CompilationUnitId {
93    /// The underlying string representation of the ID.
94    pub repr: String,
95}
96
97impl From<String> for CompilationUnitId {
98    fn from(repr: String) -> Self {
99        Self { repr }
100    }
101}
102
103impl fmt::Display for CompilationUnitId {
104    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
105        fmt::Display::fmt(&self.repr, f)
106    }
107}
108
109/// An "opaque" identifier for a compilation unit component.
110/// It is possible to inspect the `repr` field if the need arises,
111/// but its precise format is an implementation detail and is subject to change.
112///
113/// [`CompilationUnitMetadata`] can be indexed by [`CompilationUnitComponentId`].
114#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
115#[serde(transparent)]
116pub struct CompilationUnitComponentId {
117    /// The underlying string representation of the ID.
118    pub repr: String,
119}
120
121impl From<String> for CompilationUnitComponentId {
122    fn from(repr: String) -> Self {
123        Self { repr }
124    }
125}
126
127impl fmt::Display for CompilationUnitComponentId {
128    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
129        fmt::Display::fmt(&self.repr, f)
130    }
131}
132
133fn current_profile_default() -> String {
134    "release".to_string()
135}
136fn profiles_default() -> Vec<String> {
137    vec!["release".to_string()]
138}
139
140/// Top level data structure printed by `scarb metadata`.
141#[derive(Clone, Serialize, Deserialize, Debug, Eq, PartialEq)]
142#[cfg_attr(feature = "builder", derive(Builder))]
143#[cfg_attr(feature = "builder", builder(setter(into)))]
144#[non_exhaustive]
145pub struct Metadata {
146    /// The metadata format version.
147    ///
148    /// This struct will not deserialize if version does not match.
149    #[cfg_attr(feature = "builder", builder(setter(skip)))]
150    pub version: VersionPin,
151
152    /// Path to `scarb` executable.
153    pub app_exe: Option<PathBuf>,
154
155    /// Scarb's version.
156    pub app_version_info: VersionInfo,
157
158    /// Path to the _target_ (_build_) directory if known by Scarb at the moment of generating
159    /// metadata.
160    pub target_dir: Option<Utf8PathBuf>,
161
162    /// Path to the manifest of package or workspace that Scarb has been run from.
163    ///
164    /// ## Compatibility
165    ///
166    /// With very old Scarb versions (`<0.5.0`), this field may end up being empty path upon
167    /// deserializing from `scarb metadata` call.
168    /// In this case, fall back to [`WorkspaceMetadata.manifest`][WorkspaceMetadata] field value.
169    #[serde(default)]
170    pub runtime_manifest: Utf8PathBuf,
171
172    /// Current workspace metadata.
173    pub workspace: WorkspaceMetadata,
174
175    /// Metadata of all packages used in this workspace, or just members of it if this is an output
176    /// of `scarb metadata --no-deps`.
177    ///
178    /// In the former case, use [`WorkspaceMetadata::members`] to filter workspace members.
179    pub packages: Vec<PackageMetadata>,
180
181    /// List of all Scarb compilation units produced in this workspace.
182    pub compilation_units: Vec<CompilationUnitMetadata>,
183
184    /// Name of the currently selected profile
185    #[serde(default = "current_profile_default")]
186    pub current_profile: String,
187
188    /// List of all available profiles names
189    #[serde(default = "profiles_default")]
190    pub profiles: Vec<String>,
191
192    /// Additional data not captured by deserializer.
193    #[cfg_attr(feature = "builder", builder(default))]
194    #[serde(flatten)]
195    pub extra: HashMap<String, serde_json::Value>,
196}
197
198/// Current workspace metadata.
199#[derive(Clone, Serialize, Deserialize, Debug, Eq, PartialEq)]
200#[cfg_attr(feature = "builder", derive(Builder))]
201#[cfg_attr(feature = "builder", builder(setter(into)))]
202#[non_exhaustive]
203pub struct WorkspaceMetadata {
204    /// Path to the manifest file defining this workspace.
205    pub manifest_path: Utf8PathBuf,
206
207    /// Path to the directory containing this workspace.
208    pub root: Utf8PathBuf,
209
210    /// List of IDs of all packages that are members of this workspace.
211    pub members: Vec<PackageId>,
212
213    /// Additional data not captured by deserializer.
214    #[cfg_attr(feature = "builder", builder(default))]
215    #[serde(flatten)]
216    pub extra: HashMap<String, serde_json::Value>,
217}
218
219/// Metadata of single Scarb package.
220#[derive(Clone, Serialize, Deserialize, Debug, Eq, PartialEq)]
221#[cfg_attr(feature = "builder", derive(Builder))]
222#[cfg_attr(feature = "builder", builder(setter(into)))]
223#[non_exhaustive]
224pub struct PackageMetadata {
225    /// Package ID.
226    pub id: PackageId,
227
228    /// Package name as given in `Scarb.toml`.
229    pub name: String,
230
231    /// Package version as given in `Scarb.toml`.
232    pub version: Version,
233
234    /// Package edition as given in `Scarb.toml`.
235    #[serde(skip_serializing_if = "Option::is_none")]
236    pub edition: Option<String>,
237
238    /// The source of the package.
239    pub source: SourceId,
240
241    /// Path to the manifest file defining this package.
242    pub manifest_path: Utf8PathBuf,
243
244    /// Path to the directory containing this package.
245    pub root: Utf8PathBuf,
246
247    /// List of dependencies of this particular package.
248    pub dependencies: Vec<DependencyMetadata>,
249
250    /// Targets provided by the package. (`lib`, `starknet-contract`, etc.).
251    pub targets: Vec<TargetMetadata>,
252
253    /// Various metadata fields from `Scarb.toml`.
254    #[serde(flatten)]
255    pub manifest_metadata: ManifestMetadata,
256
257    /// Compiler experimental features allowed for this package.
258    #[serde(default)]
259    pub experimental_features: Vec<String>,
260
261    /// Additional data not captured by deserializer.
262    #[cfg_attr(feature = "builder", builder(default))]
263    #[serde(flatten)]
264    pub extra: HashMap<String, serde_json::Value>,
265}
266
267/// Dependency kind.
268#[derive(Clone, Serialize, Deserialize, Debug, Eq, PartialEq)]
269#[serde(rename_all = "kebab-case")]
270pub enum DepKind {
271    /// Development dependency.
272    Dev,
273}
274
275/// Scarb package dependency specification.
276///
277/// Only the `name` field is strictly sourced from `Scarb.toml`, the rest is processed by Scarb
278/// when processing this file.
279#[derive(Clone, Serialize, Deserialize, Debug, Eq, PartialEq)]
280#[cfg_attr(feature = "builder", derive(Builder))]
281#[cfg_attr(feature = "builder", builder(setter(into)))]
282#[non_exhaustive]
283pub struct DependencyMetadata {
284    /// Package name.
285    pub name: String,
286    /// Package version requirement.
287    pub version_req: VersionReq,
288    /// Package source.
289    pub source: SourceId,
290    /// Dependency kind. None denotes normal dependency.
291    pub kind: Option<DepKind>,
292    /// Features to be enabled for this dependency.
293    pub features: Option<Vec<String>>,
294    /// Whether to use default features.
295    pub default_features: Option<bool>,
296
297    /// Additional data not captured by deserializer.
298    #[cfg_attr(feature = "builder", builder(default))]
299    #[serde(flatten)]
300    pub extra: HashMap<String, serde_json::Value>,
301}
302
303/// Package target information.
304#[derive(Clone, Serialize, Deserialize, Debug, Eq, PartialEq)]
305#[cfg_attr(feature = "builder", derive(Builder))]
306#[cfg_attr(feature = "builder", builder(setter(into)))]
307#[non_exhaustive]
308pub struct TargetMetadata {
309    /// Target kind: `lib`, `starknet-contract`, etc.
310    pub kind: String,
311    /// Target name, often this is a default, which is the package name.
312    pub name: String,
313    /// Path to the main source file of the target.
314    pub source_path: Utf8PathBuf,
315    /// Unstructured target parameters, excluding default values.
316    ///
317    /// Default values are omitted because they are unknown to Scarb, they are applied by compilers.
318    pub params: serde_json::Value,
319
320    /// Additional data not captured by deserializer.
321    #[cfg_attr(feature = "builder", builder(default))]
322    #[serde(flatten)]
323    pub extra: HashMap<String, serde_json::Value>,
324}
325
326/// Scarb compilation unit information.
327#[derive(Clone, Serialize, Deserialize, Debug, Eq, PartialEq)]
328#[cfg_attr(feature = "builder", derive(Builder))]
329#[cfg_attr(feature = "builder", builder(setter(into)))]
330#[non_exhaustive]
331pub struct CompilationUnitMetadata {
332    /// Unique ID of this compilation unit.
333    pub id: CompilationUnitId,
334
335    /// Main package to be built.
336    pub package: PackageId,
337
338    /// Selected target of the main package.
339    pub target: TargetMetadata,
340
341    /// Cairo compiler config.
342    ///
343    /// This is unstructured, because this can rapidly change throughout Scarb lifetime.
344    pub compiler_config: serde_json::Value,
345
346    // TODO(mkaput): Perhaps rename this back to `components` in Scarb >=0.3?
347    /// List of all components to include in this compilation.
348    #[serde(rename = "components_data")]
349    pub components: Vec<CompilationUnitComponentMetadata>,
350
351    /// List of all Cairo compiler plugins to load in this compilation.
352    #[serde(default)]
353    pub cairo_plugins: Vec<CompilationUnitCairoPluginMetadata>,
354
355    /// Items for the Cairo's `#[cfg(...)]` attribute to be enabled in this unit.
356    #[serde(default)]
357    pub cfg: Vec<Cfg>,
358
359    /// Additional data not captured by deserializer.
360    #[cfg_attr(feature = "builder", builder(default))]
361    #[serde(flatten)]
362    pub extra: HashMap<String, serde_json::Value>,
363}
364
365/// Information to pass to the Cairo compiler about a package that is a component of a compilation
366/// unit.
367///
368/// List of components can be used to construct the `[crate_roots]` section of `cairo_project.toml`.
369#[derive(Clone, Serialize, Deserialize, Debug, Eq, PartialEq)]
370#[cfg_attr(feature = "builder", derive(Builder))]
371#[cfg_attr(feature = "builder", builder(setter(into)))]
372#[non_exhaustive]
373pub struct CompilationUnitComponentMetadata {
374    /// Package ID.
375    pub package: PackageId,
376    /// Name of the package to pass to the Cairo compiler.
377    ///
378    /// This may not be equal to Scarb package name in the future.
379    pub name: String,
380    /// Path to the root Cairo source file.
381    pub source_path: Utf8PathBuf,
382    /// Items for the Cairo's `#[cfg(...)]` attribute to be enabled in this component.
383    ///
384    /// If not specified, the one from `CompilationUnit` will be used.
385    #[serde(default)]
386    pub cfg: Option<Vec<Cfg>>,
387    /// Identifier of this component. It is unique in its compilation unit.
388    pub id: Option<CompilationUnitComponentId>,
389    /// Identifier of this component as a dependency.
390    /// This directly translates to a `discriminator` field in Cairo compiler terminology.
391    /// If [`CompilationUnitComponentMetadata.id`] is [`Some`]
392    /// then this field is [`None`] for `core` crate **only**.
393    pub discriminator: Option<String>,
394    /// Dependencies of this component.
395    /// Contains libraries and plugins, represented uniquely in the scope of the compilation unit.
396    pub dependencies: Option<Vec<CompilationUnitComponentDependencyMetadata>>,
397
398    /// Additional data not captured by deserializer.
399    #[cfg_attr(feature = "builder", builder(default))]
400    #[serde(flatten)]
401    pub extra: HashMap<String, serde_json::Value>,
402}
403
404/// Information about dependency of a component of a compilation unit.
405#[derive(Clone, Serialize, Deserialize, Debug, Eq, PartialEq)]
406#[cfg_attr(feature = "builder", derive(Builder))]
407#[cfg_attr(feature = "builder", builder(setter(into)))]
408#[non_exhaustive]
409pub struct CompilationUnitComponentDependencyMetadata {
410    /// An id of a component from the same compilation unit that this dependency refers to.
411    /// It represents either a library or a plugin. It is guaranteed to be unique
412    /// in the scope of the compilation unit.
413    pub id: CompilationUnitComponentId,
414
415    /// Additional data not captured by deserializer.
416    #[cfg_attr(feature = "builder", builder(default))]
417    #[serde(flatten)]
418    pub extra: HashMap<String, serde_json::Value>,
419}
420
421/// Information about compiler plugin to load into the Cairo compiler as part of a compilation unit.
422#[derive(Clone, Serialize, Deserialize, Debug, Eq, PartialEq)]
423#[cfg_attr(feature = "builder", derive(Builder))]
424#[cfg_attr(feature = "builder", builder(setter(into)))]
425#[non_exhaustive]
426pub struct CompilationUnitCairoPluginMetadata {
427    /// Package ID.
428    pub package: PackageId,
429
430    /// An id which uniquely identifies the plugin in scope of the compilation unit
431    /// amongst other plugins and CU components.
432    /// It is used to identify the plugin as a possible dependency of a CU component.
433    pub component_dependency_id: Option<CompilationUnitComponentId>,
434
435    /// Whether Scarb will attempt to load prebuilt binaries associated with this plugin.
436    pub prebuilt_allowed: Option<bool>,
437
438    /// Additional data not captured by deserializer.
439    #[cfg_attr(feature = "builder", builder(default))]
440    #[serde(flatten)]
441    pub extra: HashMap<String, serde_json::Value>,
442}
443
444/// Various metadata fields from package manifest.
445#[derive(Clone, Serialize, Deserialize, Debug, Default, Eq, PartialEq)]
446#[cfg_attr(feature = "builder", derive(Builder))]
447#[cfg_attr(feature = "builder", builder(setter(into)))]
448#[non_exhaustive]
449pub struct ManifestMetadata {
450    /// List of the people or organizations that are considered the "authors" of the package.
451    pub authors: Option<Vec<String>>,
452    /// A short blurb about the package.
453    pub description: Option<String>,
454    /// A URL to a website hosting the crate's documentation.
455    pub documentation: Option<String>,
456    /// A URL to a site that is the home page for this package.
457    pub homepage: Option<String>,
458    /// An array of strings that describe this package.
459    pub keywords: Option<Vec<String>>,
460    /// Name of the software license that the package is released under.
461    ///
462    /// Should be an [SPDX 2 license expression(opens in a new tab)](https://spdx.github.io/spdx-spec/v2.3/SPDX-license-expressions/),
463    /// but this is not validated neither by this crate nor Scarb.
464    pub license: Option<String>,
465    /// A path to a file containing the text of package's license (relative to its `Scarb.toml`).
466    pub license_file: Option<String>,
467    /// A path to a file in the package root (relative to its `Scarb.toml`) that contains general
468    /// information about the package.
469    pub readme: Option<String>,
470    /// A URL to the source repository for this package.
471    pub repository: Option<String>,
472    /// A map of additional internet links related to this package.
473    pub urls: Option<BTreeMap<String, String>>,
474    /// Various unstructured metadata to be used by external tools.
475    pub tool: Option<BTreeMap<String, serde_json::Value>>,
476}
477
478/// Scarb's version.
479#[derive(Clone, Serialize, Deserialize, Debug, Eq, PartialEq)]
480#[cfg_attr(feature = "builder", derive(Builder))]
481#[cfg_attr(feature = "builder", builder(setter(into)))]
482#[non_exhaustive]
483pub struct VersionInfo {
484    /// Version of Scarb.
485    pub version: Version,
486    /// Version about Git commit of Scarb if known.
487    pub commit_info: Option<CommitInfo>,
488    /// Version of the Cairo compiler bundled in Scarb.
489    pub cairo: CairoVersionInfo,
490
491    /// Additional data not captured by deserializer.
492    #[cfg_attr(feature = "builder", builder(default))]
493    #[serde(flatten)]
494    pub extra: HashMap<String, serde_json::Value>,
495}
496
497/// Cairo's version.
498#[derive(Clone, Serialize, Deserialize, Debug, Eq, PartialEq)]
499#[cfg_attr(feature = "builder", derive(Builder))]
500#[cfg_attr(feature = "builder", builder(setter(into)))]
501#[non_exhaustive]
502pub struct CairoVersionInfo {
503    /// Version of the Cairo compiler.
504    pub version: Version,
505    /// Version about Git commit of Cairo if known.
506    pub commit_info: Option<CommitInfo>,
507
508    /// Additional data not captured by deserializer.
509    #[cfg_attr(feature = "builder", builder(default))]
510    #[serde(flatten)]
511    pub extra: HashMap<String, serde_json::Value>,
512}
513
514/// Information about the Git repository where Scarb or Cairo was built from.
515#[derive(Clone, Serialize, Deserialize, Debug, Eq, PartialEq)]
516#[cfg_attr(feature = "builder", derive(Builder))]
517#[cfg_attr(feature = "builder", builder(setter(into)))]
518#[non_exhaustive]
519pub struct CommitInfo {
520    /// Git commit hash, shortened.
521    pub short_commit_hash: String,
522    /// Git commit hash.
523    pub commit_hash: String,
524    /// Commit author date if known.
525    pub commit_date: Option<String>,
526}
527
528/// Option for the `#[cfg(...)]` language attribute.
529#[derive(Clone, Serialize, Deserialize, Debug, Eq, PartialEq)]
530#[serde(untagged)]
531pub enum Cfg {
532    /// `#[cfg(key: value)`]
533    KV(String, String),
534    /// `#[cfg(name)`]
535    Name(String),
536}
537
538impl Metadata {
539    /// Returns reference to [`PackageMetadata`] corresponding to the [`PackageId`].
540    pub fn get_package(&self, id: &PackageId) -> Option<&PackageMetadata> {
541        self.packages.iter().find(|p| p.id == *id)
542    }
543
544    /// Checks if the plugin associated with [`CompilationUnitCairoPluginMetadata`] is built-in into Scarb.
545    pub fn is_builtin_plugin(&self, plugin: &CompilationUnitCairoPluginMetadata) -> Option<bool> {
546        self.get_package(&plugin.package)?
547            .targets
548            .iter()
549            .find(|&target| target.kind == "cairo-plugin")
550            .map(|target| {
551                target
552                    .params
553                    .get("builtin")
554                    .and_then(|value| value.as_bool())
555                    .unwrap_or_default()
556            })
557    }
558
559    /// Returns reference to [`CompilationUnitMetadata`] corresponding to the [`CompilationUnitId`].
560    pub fn get_compilation_unit(&self, id: &CompilationUnitId) -> Option<&CompilationUnitMetadata> {
561        self.compilation_units.iter().find(|p| p.id == *id)
562    }
563}
564
565impl<'a> Index<&'a PackageId> for Metadata {
566    type Output = PackageMetadata;
567
568    fn index(&self, idx: &'a PackageId) -> &Self::Output {
569        self.get_package(idx)
570            .unwrap_or_else(|| panic!("no package with this ID: {idx}"))
571    }
572}
573
574impl<'a> Index<&'a CompilationUnitId> for Metadata {
575    type Output = CompilationUnitMetadata;
576
577    fn index(&self, idx: &'a CompilationUnitId) -> &Self::Output {
578        self.get_compilation_unit(idx)
579            .unwrap_or_else(|| panic!("no compilation unit with this ID: {idx}"))
580    }
581}
582
583impl PackageMetadata {
584    /// Get value of the `[tool.*]` section in this package's manifest, for specific `tool_name`,
585    /// including any transformations applied by Scarb.
586    pub fn tool_metadata(&self, tool_name: &str) -> Option<&serde_json::Value> {
587        self.manifest_metadata.tool.as_ref()?.get(tool_name)
588    }
589}
590
591impl TargetMetadata {
592    /// Path to the main source directory of the target.
593    pub fn source_root(&self) -> &Utf8Path {
594        self.source_path
595            .parent()
596            .expect("Source path is guaranteed to point to a file.")
597    }
598}
599
600impl CompilationUnitComponentMetadata {
601    /// Path to the source directory of the component.
602    pub fn source_root(&self) -> &Utf8Path {
603        self.source_path
604            .parent()
605            .expect("Source path is guaranteed to point to a file.")
606    }
607}
608
609impl<'a> Index<&'a CompilationUnitComponentId> for CompilationUnitMetadata {
610    type Output = CompilationUnitComponentMetadata;
611
612    fn index(&self, idx: &'a CompilationUnitComponentId) -> &Self::Output {
613        self.components
614            .iter()
615            .find(|p| p.id.as_ref() == Some(idx))
616            .unwrap_or_else(|| panic!("no compilation unit with this ID: {idx}"))
617    }
618}