cargo_metadata/
lib.rs

1#![deny(missing_docs)]
2//! Structured access to the output of `cargo metadata` and `cargo --message-format=json`.
3//! Usually used from within a `cargo-*` executable
4//!
5//! See the [cargo book](https://doc.rust-lang.org/cargo/index.html) for
6//! details on cargo itself.
7//!
8//! ## Examples
9//!
10//! ```rust
11//! # extern crate cargo_metadata;
12//! # use std::path::Path;
13//! let mut args = std::env::args().skip_while(|val| !val.starts_with("--manifest-path"));
14//!
15//! let mut cmd = cargo_metadata::MetadataCommand::new();
16//! let manifest_path = match args.next() {
17//!     Some(ref p) if p == "--manifest-path" => {
18//!         cmd.manifest_path(args.next().unwrap());
19//!     }
20//!     Some(p) => {
21//!         cmd.manifest_path(p.trim_start_matches("--manifest-path="));
22//!     }
23//!     None => {}
24//! };
25//!
26//! let _metadata = cmd.exec().unwrap();
27//! ```
28//!
29//! Pass features flags
30//!
31//! ```rust
32//! # // This should be kept in sync with the equivalent example in the readme.
33//! # extern crate cargo_metadata;
34//! # use std::path::Path;
35//! # fn main() {
36//! use cargo_metadata::{MetadataCommand, CargoOpt};
37//!
38//! let _metadata = MetadataCommand::new()
39//!     .manifest_path("./Cargo.toml")
40//!     .features(CargoOpt::AllFeatures)
41//!     .exec()
42//!     .unwrap();
43//! # }
44//! ```
45//!
46//! Parse message-format output:
47//!
48//! ```
49//! # extern crate cargo_metadata;
50//! use std::process::{Stdio, Command};
51//! use cargo_metadata::Message;
52//!
53//! let mut command = Command::new("cargo")
54//!     .args(&["build", "--message-format=json-render-diagnostics"])
55//!     .stdout(Stdio::piped())
56//!     .spawn()
57//!     .unwrap();
58//!
59//! let reader = std::io::BufReader::new(command.stdout.take().unwrap());
60//! for message in cargo_metadata::Message::parse_stream(reader) {
61//!     match message.unwrap() {
62//!         Message::CompilerMessage(msg) => {
63//!             println!("{:?}", msg);
64//!         },
65//!         Message::CompilerArtifact(artifact) => {
66//!             println!("{:?}", artifact);
67//!         },
68//!         Message::BuildScriptExecuted(script) => {
69//!             println!("{:?}", script);
70//!         },
71//!         Message::BuildFinished(finished) => {
72//!             println!("{:?}", finished);
73//!         },
74//!         _ => () // Unknown message
75//!     }
76//! }
77//!
78//! let output = command.wait().expect("Couldn't get cargo's exit status");
79//! ```
80
81use camino::Utf8PathBuf;
82#[cfg(feature = "builder")]
83use derive_builder::Builder;
84use std::collections::HashMap;
85use std::env;
86use std::fmt;
87use std::path::PathBuf;
88use std::process::Command;
89use std::str::from_utf8;
90
91pub use camino;
92pub use semver::{Version, VersionReq};
93
94pub use dependency::{Dependency, DependencyKind};
95use diagnostic::Diagnostic;
96pub use errors::{Error, Result};
97#[allow(deprecated)]
98pub use messages::parse_messages;
99pub use messages::{
100    Artifact, ArtifactProfile, BuildFinished, BuildScript, CompilerMessage, Message, MessageIter,
101};
102use serde::{Deserialize, Serialize};
103
104mod dependency;
105pub mod diagnostic;
106mod errors;
107mod messages;
108
109/// An "opaque" identifier for a package.
110/// It is possible to inspect the `repr` field, if the need arises, but its
111/// precise format is an implementation detail and is subject to change.
112///
113/// `Metadata` can be indexed by `PackageId`.
114#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
115#[serde(transparent)]
116pub struct PackageId {
117    /// The underlying string representation of id.
118    pub repr: String,
119}
120
121impl std::fmt::Display for PackageId {
122    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
123        fmt::Display::fmt(&self.repr, f)
124    }
125}
126
127// Helpers for default metadata fields
128fn is_null(value: &serde_json::Value) -> bool {
129    match value {
130        serde_json::Value::Null => true,
131        _ => false,
132    }
133}
134
135#[derive(Clone, Serialize, Deserialize, Debug)]
136#[cfg_attr(feature = "builder", derive(Builder))]
137#[non_exhaustive]
138#[cfg_attr(feature = "builder", builder(pattern = "owned", setter(into)))]
139/// Starting point for metadata returned by `cargo metadata`
140pub struct Metadata {
141    /// A list of all crates referenced by this crate (and the crate itself)
142    pub packages: Vec<Package>,
143    /// A list of all workspace members
144    pub workspace_members: Vec<PackageId>,
145    /// Dependencies graph
146    pub resolve: Option<Resolve>,
147    /// Workspace root
148    pub workspace_root: Utf8PathBuf,
149    /// Build directory
150    pub target_directory: Utf8PathBuf,
151    /// The workspace-level metadata object. Null if non-existent.
152    #[serde(rename = "metadata", default, skip_serializing_if = "is_null")]
153    pub workspace_metadata: serde_json::Value,
154    /// The metadata format version
155    version: usize,
156}
157
158impl Metadata {
159    /// Get the root package of this metadata instance.
160    pub fn root_package(&self) -> Option<&Package> {
161        let root = self.resolve.as_ref()?.root.as_ref()?;
162        self.packages.iter().find(|pkg| &pkg.id == root)
163    }
164}
165
166impl<'a> std::ops::Index<&'a PackageId> for Metadata {
167    type Output = Package;
168
169    fn index(&self, idx: &'a PackageId) -> &Package {
170        self.packages
171            .iter()
172            .find(|p| p.id == *idx)
173            .unwrap_or_else(|| panic!("no package with this id: {:?}", idx))
174    }
175}
176
177#[derive(Clone, Serialize, Deserialize, Debug)]
178#[cfg_attr(feature = "builder", derive(Builder))]
179#[non_exhaustive]
180#[cfg_attr(feature = "builder", builder(pattern = "owned", setter(into)))]
181/// A dependency graph
182pub struct Resolve {
183    /// Nodes in a dependencies graph
184    pub nodes: Vec<Node>,
185
186    /// The crate for which the metadata was read.
187    pub root: Option<PackageId>,
188}
189
190#[derive(Clone, Serialize, Deserialize, Debug)]
191#[cfg_attr(feature = "builder", derive(Builder))]
192#[non_exhaustive]
193#[cfg_attr(feature = "builder", builder(pattern = "owned", setter(into)))]
194/// A node in a dependencies graph
195pub struct Node {
196    /// An opaque identifier for a package
197    pub id: PackageId,
198    /// Dependencies in a structured format.
199    ///
200    /// `deps` handles renamed dependencies whereas `dependencies` does not.
201    #[serde(default)]
202    pub deps: Vec<NodeDep>,
203
204    /// List of opaque identifiers for this node's dependencies.
205    /// It doesn't support renamed dependencies. See `deps`.
206    pub dependencies: Vec<PackageId>,
207
208    /// Features enabled on the crate
209    #[serde(default)]
210    pub features: Vec<String>,
211}
212
213#[derive(Clone, Serialize, Deserialize, Debug)]
214#[cfg_attr(feature = "builder", derive(Builder))]
215#[non_exhaustive]
216#[cfg_attr(feature = "builder", builder(pattern = "owned", setter(into)))]
217/// A dependency in a node
218pub struct NodeDep {
219    /// The name of the dependency's library target.
220    /// If the crate was renamed, it is the new name.
221    pub name: String,
222    /// Package ID (opaque unique identifier)
223    pub pkg: PackageId,
224    /// The kinds of dependencies.
225    ///
226    /// This field was added in Rust 1.41.
227    #[serde(default)]
228    pub dep_kinds: Vec<DepKindInfo>,
229}
230
231#[derive(Clone, Serialize, Deserialize, Debug)]
232#[cfg_attr(feature = "builder", derive(Builder))]
233#[non_exhaustive]
234#[cfg_attr(feature = "builder", builder(pattern = "owned", setter(into)))]
235/// Information about a dependency kind.
236pub struct DepKindInfo {
237    /// The kind of dependency.
238    #[serde(deserialize_with = "dependency::parse_dependency_kind")]
239    pub kind: DependencyKind,
240    /// The target platform for the dependency.
241    ///
242    /// This is `None` if it is not a target dependency.
243    ///
244    /// Use the [`Display`] trait to access the contents.
245    ///
246    /// By default all platform dependencies are included in the resolve
247    /// graph. Use Cargo's `--filter-platform` flag if you only want to
248    /// include dependencies for a specific platform.
249    ///
250    /// [`Display`]: std::fmt::Display
251    pub target: Option<dependency::Platform>,
252}
253
254#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
255#[cfg_attr(feature = "builder", derive(Builder))]
256#[non_exhaustive]
257#[cfg_attr(feature = "builder", builder(pattern = "owned", setter(into)))]
258/// One or more crates described by a single `Cargo.toml`
259///
260/// Each [`target`][Package::targets] of a `Package` will be built as a crate.
261/// For more information, see <https://doc.rust-lang.org/book/ch07-01-packages-and-crates.html>.
262pub struct Package {
263    /// Name as given in the `Cargo.toml`
264    pub name: String,
265    /// Version given in the `Cargo.toml`
266    pub version: Version,
267    /// Authors given in the `Cargo.toml`
268    #[serde(default)]
269    pub authors: Vec<String>,
270    /// An opaque identifier for a package
271    pub id: PackageId,
272    /// The source of the package, e.g.
273    /// crates.io or `None` for local projects.
274    pub source: Option<Source>,
275    /// Description as given in the `Cargo.toml`
276    pub description: Option<String>,
277    /// List of dependencies of this particular package
278    pub dependencies: Vec<Dependency>,
279    /// License as given in the `Cargo.toml`
280    pub license: Option<String>,
281    /// If the package is using a nonstandard license, this key may be specified instead of
282    /// `license`, and must point to a file relative to the manifest.
283    pub license_file: Option<Utf8PathBuf>,
284    /// Targets provided by the crate (lib, bin, example, test, ...)
285    pub targets: Vec<Target>,
286    /// Features provided by the crate, mapped to the features required by that feature.
287    pub features: HashMap<String, Vec<String>>,
288    /// Path containing the `Cargo.toml`
289    pub manifest_path: Utf8PathBuf,
290    /// Categories as given in the `Cargo.toml`
291    #[serde(default)]
292    pub categories: Vec<String>,
293    /// Keywords as given in the `Cargo.toml`
294    #[serde(default)]
295    pub keywords: Vec<String>,
296    /// Readme as given in the `Cargo.toml`
297    pub readme: Option<Utf8PathBuf>,
298    /// Repository as given in the `Cargo.toml`
299    // can't use `url::Url` because that requires a more recent stable compiler
300    pub repository: Option<String>,
301    /// Homepage as given in the `Cargo.toml`
302    ///
303    /// On versions of cargo before 1.49, this will always be [`None`].
304    pub homepage: Option<String>,
305    /// Documentation URL as given in the `Cargo.toml`
306    ///
307    /// On versions of cargo before 1.49, this will always be [`None`].
308    pub documentation: Option<String>,
309    /// Default Rust edition for the package
310    ///
311    /// Beware that individual targets may specify their own edition in
312    /// [`Target::edition`].
313    #[serde(default = "edition_default")]
314    pub edition: String,
315    /// Contents of the free form package.metadata section
316    ///
317    /// This contents can be serialized to a struct using serde:
318    ///
319    /// ```rust
320    /// use serde::Deserialize;
321    /// use serde_json::json;
322    ///
323    /// #[derive(Debug, Deserialize)]
324    /// struct SomePackageMetadata {
325    ///     some_value: i32,
326    /// }
327    ///
328    /// fn main() {
329    ///     let value = json!({
330    ///         "some_value": 42,
331    ///     });
332    ///
333    ///     let package_metadata: SomePackageMetadata = serde_json::from_value(value).unwrap();
334    ///     assert_eq!(package_metadata.some_value, 42);
335    /// }
336    ///
337    /// ```
338    #[serde(default, skip_serializing_if = "is_null")]
339    pub metadata: serde_json::Value,
340    /// The name of a native library the package is linking to.
341    pub links: Option<String>,
342    /// List of registries to which this package may be published.
343    ///
344    /// Publishing is unrestricted if `None`, and forbidden if the `Vec` is empty.
345    ///
346    /// This is always `None` if running with a version of Cargo older than 1.39.
347    pub publish: Option<Vec<String>>,
348    /// The default binary to run by `cargo run`.
349    ///
350    /// This is always `None` if running with a version of Cargo older than 1.55.
351    pub default_run: Option<String>,
352    /// The minimum supported Rust version of this package.
353    ///
354    /// This is always `None` if running with a version of Cargo older than 1.58.
355    pub rust_version: Option<VersionReq>,
356}
357
358impl Package {
359    /// Full path to the license file if one is present in the manifest
360    pub fn license_file(&self) -> Option<Utf8PathBuf> {
361        self.license_file.as_ref().map(|file| {
362            self.manifest_path
363                .parent()
364                .unwrap_or(&self.manifest_path)
365                .join(file)
366        })
367    }
368
369    /// Full path to the readme file if one is present in the manifest
370    pub fn readme(&self) -> Option<Utf8PathBuf> {
371        self.readme
372            .as_ref()
373            .map(|file| self.manifest_path.join(file))
374    }
375}
376
377/// The source of a package such as crates.io.
378#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
379#[serde(transparent)]
380pub struct Source {
381    /// The underlying string representation of a source.
382    pub repr: String,
383}
384
385impl Source {
386    /// Returns true if the source is crates.io.
387    pub fn is_crates_io(&self) -> bool {
388        self.repr == "registry+https://github.com/rust-lang/crates.io-index"
389    }
390}
391
392impl std::fmt::Display for Source {
393    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
394        fmt::Display::fmt(&self.repr, f)
395    }
396}
397
398#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, Hash)]
399#[cfg_attr(feature = "builder", derive(Builder))]
400#[cfg_attr(feature = "builder", builder(pattern = "owned", setter(into)))]
401#[non_exhaustive]
402/// A single target (lib, bin, example, ...) provided by a crate
403pub struct Target {
404    /// Name as given in the `Cargo.toml` or generated from the file name
405    pub name: String,
406    /// Kind of target ("bin", "example", "test", "bench", "lib")
407    pub kind: Vec<String>,
408    /// Almost the same as `kind`, except when an example is a library instead of an executable.
409    /// In that case `crate_types` contains things like `rlib` and `dylib` while `kind` is `example`
410    #[serde(default)]
411    #[cfg_attr(feature = "builder", builder(default))]
412    pub crate_types: Vec<String>,
413
414    #[serde(default)]
415    #[cfg_attr(feature = "builder", builder(default))]
416    #[serde(rename = "required-features")]
417    /// This target is built only if these features are enabled.
418    /// It doesn't apply to `lib` targets.
419    pub required_features: Vec<String>,
420    /// Path to the main source file of the target
421    pub src_path: Utf8PathBuf,
422    /// Rust edition for this target
423    #[serde(default = "edition_default")]
424    #[cfg_attr(feature = "builder", builder(default = "edition_default()"))]
425    pub edition: String,
426    /// Whether or not this target has doc tests enabled, and the target is
427    /// compatible with doc testing.
428    ///
429    /// This is always `true` if running with a version of Cargo older than 1.37.
430    #[serde(default = "default_true")]
431    #[cfg_attr(feature = "builder", builder(default = "true"))]
432    pub doctest: bool,
433    /// Whether or not this target is tested by default by `cargo test`.
434    ///
435    /// This is always `true` if running with a version of Cargo older than 1.47.
436    #[serde(default = "default_true")]
437    #[cfg_attr(feature = "builder", builder(default = "true"))]
438    pub test: bool,
439    /// Whether or not this target is documented by `cargo doc`.
440    ///
441    /// This is always `true` if running with a version of Cargo older than 1.50.
442    #[serde(default = "default_true")]
443    #[cfg_attr(feature = "builder", builder(default = "true"))]
444    pub doc: bool,
445}
446
447fn default_true() -> bool {
448    true
449}
450
451fn edition_default() -> String {
452    "2015".to_string()
453}
454
455/// Cargo features flags
456#[derive(Debug, Clone)]
457pub enum CargoOpt {
458    /// Run cargo with `--features-all`
459    AllFeatures,
460    /// Run cargo with `--no-default-features`
461    NoDefaultFeatures,
462    /// Run cargo with `--features <FEATURES>`
463    SomeFeatures(Vec<String>),
464}
465
466/// A builder for configurating `cargo metadata` invocation.
467#[derive(Debug, Clone, Default)]
468pub struct MetadataCommand {
469    /// Path to `cargo` executable.  If not set, this will use the
470    /// the `$CARGO` environment variable, and if that is not set, will
471    /// simply be `cargo`.
472    cargo_path: Option<PathBuf>,
473    /// Path to `Cargo.toml`
474    manifest_path: Option<PathBuf>,
475    /// Current directory of the `cargo metadata` process.
476    current_dir: Option<PathBuf>,
477    /// Output information only about the root package and don't fetch dependencies.
478    no_deps: bool,
479    /// Collections of `CargoOpt::SomeFeatures(..)`
480    features: Vec<String>,
481    /// Latched `CargoOpt::AllFeatures`
482    all_features: bool,
483    /// Latched `CargoOpt::NoDefaultFeatures`
484    no_default_features: bool,
485    /// Arbitrary command line flags to pass to `cargo`.  These will be added
486    /// to the end of the command line invocation.
487    other_options: Vec<String>,
488}
489
490impl MetadataCommand {
491    /// Creates a default `cargo metadata` command, which will look for
492    /// `Cargo.toml` in the ancestors of the current directory.
493    pub fn new() -> MetadataCommand {
494        MetadataCommand::default()
495    }
496    /// Path to `cargo` executable.  If not set, this will use the
497    /// the `$CARGO` environment variable, and if that is not set, will
498    /// simply be `cargo`.
499    pub fn cargo_path(&mut self, path: impl Into<PathBuf>) -> &mut MetadataCommand {
500        self.cargo_path = Some(path.into());
501        self
502    }
503    /// Path to `Cargo.toml`
504    pub fn manifest_path(&mut self, path: impl Into<PathBuf>) -> &mut MetadataCommand {
505        self.manifest_path = Some(path.into());
506        self
507    }
508    /// Current directory of the `cargo metadata` process.
509    pub fn current_dir(&mut self, path: impl Into<PathBuf>) -> &mut MetadataCommand {
510        self.current_dir = Some(path.into());
511        self
512    }
513    /// Output information only about the root package and don't fetch dependencies.
514    pub fn no_deps(&mut self) -> &mut MetadataCommand {
515        self.no_deps = true;
516        self
517    }
518    /// Which features to include.
519    ///
520    /// Call this multiple times to specify advanced feature configurations:
521    ///
522    /// ```no_run
523    /// # use cargo_metadata::{CargoOpt, MetadataCommand};
524    /// MetadataCommand::new()
525    ///     .features(CargoOpt::NoDefaultFeatures)
526    ///     .features(CargoOpt::SomeFeatures(vec!["feat1".into(), "feat2".into()]))
527    ///     .features(CargoOpt::SomeFeatures(vec!["feat3".into()]))
528    ///     // ...
529    ///     # ;
530    /// ```
531    ///
532    /// # Panics
533    ///
534    /// `cargo metadata` rejects multiple `--no-default-features` flags. Similarly, the `features()`
535    /// method panics when specifying multiple `CargoOpt::NoDefaultFeatures`:
536    ///
537    /// ```should_panic
538    /// # use cargo_metadata::{CargoOpt, MetadataCommand};
539    /// MetadataCommand::new()
540    ///     .features(CargoOpt::NoDefaultFeatures)
541    ///     .features(CargoOpt::NoDefaultFeatures) // <-- panic!
542    ///     // ...
543    ///     # ;
544    /// ```
545    ///
546    /// The method also panics for multiple `CargoOpt::AllFeatures` arguments:
547    ///
548    /// ```should_panic
549    /// # use cargo_metadata::{CargoOpt, MetadataCommand};
550    /// MetadataCommand::new()
551    ///     .features(CargoOpt::AllFeatures)
552    ///     .features(CargoOpt::AllFeatures) // <-- panic!
553    ///     // ...
554    ///     # ;
555    /// ```
556    pub fn features(&mut self, features: CargoOpt) -> &mut MetadataCommand {
557        match features {
558            CargoOpt::SomeFeatures(features) => self.features.extend(features),
559            CargoOpt::NoDefaultFeatures => {
560                assert!(
561                    !self.no_default_features,
562                    "Do not supply CargoOpt::NoDefaultFeatures more than once!"
563                );
564                self.no_default_features = true;
565            }
566            CargoOpt::AllFeatures => {
567                assert!(
568                    !self.all_features,
569                    "Do not supply CargoOpt::AllFeatures more than once!"
570                );
571                self.all_features = true;
572            }
573        }
574        self
575    }
576    /// Arbitrary command line flags to pass to `cargo`.  These will be added
577    /// to the end of the command line invocation.
578    pub fn other_options(&mut self, options: impl Into<Vec<String>>) -> &mut MetadataCommand {
579        self.other_options = options.into();
580        self
581    }
582
583    /// Builds a command for `cargo metadata`.  This is the first
584    /// part of the work of `exec`.
585    pub fn cargo_command(&self) -> Command {
586        let cargo = self
587            .cargo_path
588            .clone()
589            .or_else(|| env::var("CARGO").map(PathBuf::from).ok())
590            .unwrap_or_else(|| PathBuf::from("cargo"));
591        let mut cmd = Command::new(cargo);
592        cmd.args(&["metadata", "--format-version", "1"]);
593
594        if self.no_deps {
595            cmd.arg("--no-deps");
596        }
597
598        if let Some(path) = self.current_dir.as_ref() {
599            cmd.current_dir(path);
600        }
601
602        if !self.features.is_empty() {
603            cmd.arg("--features").arg(self.features.join(","));
604        }
605        if self.all_features {
606            cmd.arg("--all-features");
607        }
608        if self.no_default_features {
609            cmd.arg("--no-default-features");
610        }
611
612        if let Some(manifest_path) = &self.manifest_path {
613            cmd.arg("--manifest-path").arg(manifest_path.as_os_str());
614        }
615        cmd.args(&self.other_options);
616
617        cmd
618    }
619
620    /// Parses `cargo metadata` output.  `data` must have been
621    /// produced by a command built with `cargo_command`.
622    pub fn parse<T: AsRef<str>>(data: T) -> Result<Metadata> {
623        let meta = serde_json::from_str(data.as_ref())?;
624        Ok(meta)
625    }
626
627    /// Runs configured `cargo metadata` and returns parsed `Metadata`.
628    pub fn exec(&self) -> Result<Metadata> {
629        let output = self.cargo_command().output()?;
630        if !output.status.success() {
631            return Err(Error::CargoMetadata {
632                stderr: String::from_utf8(output.stderr)?,
633            });
634        }
635        let stdout = from_utf8(&output.stdout)?
636            .lines()
637            .find(|line| line.starts_with('{'))
638            .ok_or_else(|| Error::NoJson)?;
639        Self::parse(stdout)
640    }
641}