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::{BTreeMap, HashMap};
85use std::env;
86use std::ffi::OsString;
87use std::fmt;
88use std::hash::Hash;
89use std::path::PathBuf;
90use std::process::{Command, Stdio};
91use std::str::from_utf8;
92
93pub use camino;
94pub use semver;
95use semver::{Version, VersionReq};
96
97#[cfg(feature = "builder")]
98pub use dependency::DependencyBuilder;
99pub use dependency::{Dependency, DependencyKind};
100use diagnostic::Diagnostic;
101pub use errors::{Error, Result};
102#[allow(deprecated)]
103pub use messages::parse_messages;
104pub use messages::{
105    Artifact, ArtifactProfile, BuildFinished, BuildScript, CompilerMessage, Message, MessageIter,
106};
107#[cfg(feature = "builder")]
108pub use messages::{
109    ArtifactBuilder, ArtifactProfileBuilder, BuildFinishedBuilder, BuildScriptBuilder,
110    CompilerMessageBuilder,
111};
112use serde::{Deserialize, Serialize, Serializer};
113
114mod dependency;
115pub mod diagnostic;
116mod errors;
117mod messages;
118
119/// An "opaque" identifier for a package.
120/// It is possible to inspect the `repr` field, if the need arises, but its
121/// precise format is an implementation detail and is subject to change.
122///
123/// `Metadata` can be indexed by `PackageId`.
124#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
125#[serde(transparent)]
126pub struct PackageId {
127    /// The underlying string representation of id.
128    pub repr: String,
129}
130
131impl std::fmt::Display for PackageId {
132    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
133        fmt::Display::fmt(&self.repr, f)
134    }
135}
136
137/// Helpers for default metadata fields
138fn is_null(value: &serde_json::Value) -> bool {
139    matches!(value, serde_json::Value::Null)
140}
141
142/// Helper to ensure that hashmaps serialize in sorted order, to make
143/// serialization deterministic.
144fn sorted_map<S: Serializer, K: Serialize + Ord, V: Serialize>(
145    value: &HashMap<K, V>,
146    serializer: S,
147) -> std::result::Result<S::Ok, S::Error> {
148    value
149        .iter()
150        .collect::<BTreeMap<_, _>>()
151        .serialize(serializer)
152}
153
154#[derive(Clone, Serialize, Deserialize, Debug)]
155#[cfg_attr(feature = "builder", derive(Builder))]
156#[non_exhaustive]
157#[cfg_attr(feature = "builder", builder(pattern = "owned", setter(into)))]
158/// Starting point for metadata returned by `cargo metadata`
159pub struct Metadata {
160    /// A list of all crates referenced by this crate (and the crate itself)
161    pub packages: Vec<Package>,
162    /// A list of all workspace members
163    pub workspace_members: Vec<PackageId>,
164    /// Dependencies graph
165    pub resolve: Option<Resolve>,
166    /// Workspace root
167    pub workspace_root: Utf8PathBuf,
168    /// Build directory
169    pub target_directory: Utf8PathBuf,
170    /// The workspace-level metadata object. Null if non-existent.
171    #[serde(rename = "metadata", default, skip_serializing_if = "is_null")]
172    pub workspace_metadata: serde_json::Value,
173    /// The metadata format version
174    version: usize,
175}
176
177impl Metadata {
178    /// Get the workspace's root package of this metadata instance.
179    pub fn root_package(&self) -> Option<&Package> {
180        match &self.resolve {
181            Some(resolve) => {
182                // if dependencies are resolved, use Cargo's answer
183                let root = resolve.root.as_ref()?;
184                self.packages.iter().find(|pkg| &pkg.id == root)
185            }
186            None => {
187                // if dependencies aren't resolved, check for a root package manually
188                let root_manifest_path = self.workspace_root.join("Cargo.toml");
189                self.packages
190                    .iter()
191                    .find(|pkg| pkg.manifest_path == root_manifest_path)
192            }
193        }
194    }
195
196    /// Get the workspace packages.
197    pub fn workspace_packages(&self) -> Vec<&Package> {
198        self.packages
199            .iter()
200            .filter(|&p| self.workspace_members.contains(&p.id))
201            .collect()
202    }
203}
204
205impl<'a> std::ops::Index<&'a PackageId> for Metadata {
206    type Output = Package;
207
208    fn index(&self, idx: &'a PackageId) -> &Package {
209        self.packages
210            .iter()
211            .find(|p| p.id == *idx)
212            .unwrap_or_else(|| panic!("no package with this id: {:?}", idx))
213    }
214}
215
216#[derive(Clone, Serialize, Deserialize, Debug)]
217#[cfg_attr(feature = "builder", derive(Builder))]
218#[non_exhaustive]
219#[cfg_attr(feature = "builder", builder(pattern = "owned", setter(into)))]
220/// A dependency graph
221pub struct Resolve {
222    /// Nodes in a dependencies graph
223    pub nodes: Vec<Node>,
224
225    /// The crate for which the metadata was read.
226    pub root: Option<PackageId>,
227}
228
229#[derive(Clone, Serialize, Deserialize, Debug)]
230#[cfg_attr(feature = "builder", derive(Builder))]
231#[non_exhaustive]
232#[cfg_attr(feature = "builder", builder(pattern = "owned", setter(into)))]
233/// A node in a dependencies graph
234pub struct Node {
235    /// An opaque identifier for a package
236    pub id: PackageId,
237    /// Dependencies in a structured format.
238    ///
239    /// `deps` handles renamed dependencies whereas `dependencies` does not.
240    #[serde(default)]
241    pub deps: Vec<NodeDep>,
242
243    /// List of opaque identifiers for this node's dependencies.
244    /// It doesn't support renamed dependencies. See `deps`.
245    pub dependencies: Vec<PackageId>,
246
247    /// Features enabled on the crate
248    #[serde(default)]
249    pub features: Vec<String>,
250}
251
252#[derive(Clone, Serialize, Deserialize, Debug)]
253#[cfg_attr(feature = "builder", derive(Builder))]
254#[non_exhaustive]
255#[cfg_attr(feature = "builder", builder(pattern = "owned", setter(into)))]
256/// A dependency in a node
257pub struct NodeDep {
258    /// The name of the dependency's library target.
259    /// If the crate was renamed, it is the new name.
260    pub name: String,
261    /// Package ID (opaque unique identifier)
262    pub pkg: PackageId,
263    /// The kinds of dependencies.
264    ///
265    /// This field was added in Rust 1.41.
266    #[serde(default)]
267    pub dep_kinds: Vec<DepKindInfo>,
268}
269
270#[derive(Clone, Serialize, Deserialize, Debug)]
271#[cfg_attr(feature = "builder", derive(Builder))]
272#[non_exhaustive]
273#[cfg_attr(feature = "builder", builder(pattern = "owned", setter(into)))]
274/// Information about a dependency kind.
275pub struct DepKindInfo {
276    /// The kind of dependency.
277    #[serde(deserialize_with = "dependency::parse_dependency_kind")]
278    pub kind: DependencyKind,
279    /// The target platform for the dependency.
280    ///
281    /// This is `None` if it is not a target dependency.
282    ///
283    /// Use the [`Display`] trait to access the contents.
284    ///
285    /// By default all platform dependencies are included in the resolve
286    /// graph. Use Cargo's `--filter-platform` flag if you only want to
287    /// include dependencies for a specific platform.
288    ///
289    /// [`Display`]: std::fmt::Display
290    pub target: Option<dependency::Platform>,
291}
292
293#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
294#[cfg_attr(feature = "builder", derive(Builder))]
295#[non_exhaustive]
296#[cfg_attr(feature = "builder", builder(pattern = "owned", setter(into)))]
297/// One or more crates described by a single `Cargo.toml`
298///
299/// Each [`target`][Package::targets] of a `Package` will be built as a crate.
300/// For more information, see <https://doc.rust-lang.org/book/ch07-01-packages-and-crates.html>.
301pub struct Package {
302    /// Name as given in the `Cargo.toml`
303    pub name: String,
304    /// Version given in the `Cargo.toml`
305    pub version: Version,
306    /// Authors given in the `Cargo.toml`
307    #[serde(default)]
308    pub authors: Vec<String>,
309    /// An opaque identifier for a package
310    pub id: PackageId,
311    /// The source of the package, e.g.
312    /// crates.io or `None` for local projects.
313    pub source: Option<Source>,
314    /// Description as given in the `Cargo.toml`
315    pub description: Option<String>,
316    /// List of dependencies of this particular package
317    pub dependencies: Vec<Dependency>,
318    /// License as given in the `Cargo.toml`
319    pub license: Option<String>,
320    /// If the package is using a nonstandard license, this key may be specified instead of
321    /// `license`, and must point to a file relative to the manifest.
322    pub license_file: Option<Utf8PathBuf>,
323    /// Targets provided by the crate (lib, bin, example, test, ...)
324    pub targets: Vec<Target>,
325    /// Features provided by the crate, mapped to the features required by that feature.
326    #[serde(serialize_with = "sorted_map")]
327    pub features: HashMap<String, Vec<String>>,
328    /// Path containing the `Cargo.toml`
329    pub manifest_path: Utf8PathBuf,
330    /// Categories as given in the `Cargo.toml`
331    #[serde(default)]
332    pub categories: Vec<String>,
333    /// Keywords as given in the `Cargo.toml`
334    #[serde(default)]
335    pub keywords: Vec<String>,
336    /// Readme as given in the `Cargo.toml`
337    pub readme: Option<Utf8PathBuf>,
338    /// Repository as given in the `Cargo.toml`
339    // can't use `url::Url` because that requires a more recent stable compiler
340    pub repository: Option<String>,
341    /// Homepage as given in the `Cargo.toml`
342    ///
343    /// On versions of cargo before 1.49, this will always be [`None`].
344    pub homepage: Option<String>,
345    /// Documentation URL as given in the `Cargo.toml`
346    ///
347    /// On versions of cargo before 1.49, this will always be [`None`].
348    pub documentation: Option<String>,
349    /// Default Rust edition for the package
350    ///
351    /// Beware that individual targets may specify their own edition in
352    /// [`Target::edition`].
353    #[serde(default)]
354    pub edition: Edition,
355    /// Contents of the free form package.metadata section
356    ///
357    /// This contents can be serialized to a struct using serde:
358    ///
359    /// ```rust
360    /// use serde::Deserialize;
361    /// use serde_json::json;
362    ///
363    /// #[derive(Debug, Deserialize)]
364    /// struct SomePackageMetadata {
365    ///     some_value: i32,
366    /// }
367    ///
368    /// fn main() {
369    ///     let value = json!({
370    ///         "some_value": 42,
371    ///     });
372    ///
373    ///     let package_metadata: SomePackageMetadata = serde_json::from_value(value).unwrap();
374    ///     assert_eq!(package_metadata.some_value, 42);
375    /// }
376    ///
377    /// ```
378    #[serde(default, skip_serializing_if = "is_null")]
379    pub metadata: serde_json::Value,
380    /// The name of a native library the package is linking to.
381    pub links: Option<String>,
382    /// List of registries to which this package may be published.
383    ///
384    /// Publishing is unrestricted if `None`, and forbidden if the `Vec` is empty.
385    ///
386    /// This is always `None` if running with a version of Cargo older than 1.39.
387    pub publish: Option<Vec<String>>,
388    /// The default binary to run by `cargo run`.
389    ///
390    /// This is always `None` if running with a version of Cargo older than 1.55.
391    pub default_run: Option<String>,
392    /// The minimum supported Rust version of this package.
393    ///
394    /// This is always `None` if running with a version of Cargo older than 1.58.
395    pub rust_version: Option<VersionReq>,
396}
397
398impl Package {
399    /// Full path to the license file if one is present in the manifest
400    pub fn license_file(&self) -> Option<Utf8PathBuf> {
401        self.license_file.as_ref().map(|file| {
402            self.manifest_path
403                .parent()
404                .unwrap_or(&self.manifest_path)
405                .join(file)
406        })
407    }
408
409    /// Full path to the readme file if one is present in the manifest
410    pub fn readme(&self) -> Option<Utf8PathBuf> {
411        self.readme.as_ref().map(|file| {
412            self.manifest_path
413                .parent()
414                .unwrap_or(&self.manifest_path)
415                .join(file)
416        })
417    }
418}
419
420/// The source of a package such as crates.io.
421#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
422#[serde(transparent)]
423pub struct Source {
424    /// The underlying string representation of a source.
425    pub repr: String,
426}
427
428impl Source {
429    /// Returns true if the source is crates.io.
430    pub fn is_crates_io(&self) -> bool {
431        self.repr == "registry+https://github.com/rust-lang/crates.io-index"
432    }
433}
434
435impl std::fmt::Display for Source {
436    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
437        fmt::Display::fmt(&self.repr, f)
438    }
439}
440
441#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, Hash)]
442#[cfg_attr(feature = "builder", derive(Builder))]
443#[cfg_attr(feature = "builder", builder(pattern = "owned", setter(into)))]
444#[non_exhaustive]
445/// A single target (lib, bin, example, ...) provided by a crate
446pub struct Target {
447    /// Name as given in the `Cargo.toml` or generated from the file name
448    pub name: String,
449    /// Kind of target ("bin", "example", "test", "bench", "lib", "custom-build")
450    pub kind: Vec<String>,
451    /// Almost the same as `kind`, except when an example is a library instead of an executable.
452    /// In that case `crate_types` contains things like `rlib` and `dylib` while `kind` is `example`
453    #[serde(default)]
454    #[cfg_attr(feature = "builder", builder(default))]
455    pub crate_types: Vec<String>,
456
457    #[serde(default)]
458    #[cfg_attr(feature = "builder", builder(default))]
459    #[serde(rename = "required-features")]
460    /// This target is built only if these features are enabled.
461    /// It doesn't apply to `lib` targets.
462    pub required_features: Vec<String>,
463    /// Path to the main source file of the target
464    pub src_path: Utf8PathBuf,
465    /// Rust edition for this target
466    #[serde(default)]
467    #[cfg_attr(feature = "builder", builder(default))]
468    pub edition: Edition,
469    /// Whether or not this target has doc tests enabled, and the target is
470    /// compatible with doc testing.
471    ///
472    /// This is always `true` if running with a version of Cargo older than 1.37.
473    #[serde(default = "default_true")]
474    #[cfg_attr(feature = "builder", builder(default = "true"))]
475    pub doctest: bool,
476    /// Whether or not this target is tested by default by `cargo test`.
477    ///
478    /// This is always `true` if running with a version of Cargo older than 1.47.
479    #[serde(default = "default_true")]
480    #[cfg_attr(feature = "builder", builder(default = "true"))]
481    pub test: bool,
482    /// Whether or not this target is documented by `cargo doc`.
483    ///
484    /// This is always `true` if running with a version of Cargo older than 1.50.
485    #[serde(default = "default_true")]
486    #[cfg_attr(feature = "builder", builder(default = "true"))]
487    pub doc: bool,
488}
489
490impl Target {
491    fn is_kind(&self, name: &str) -> bool {
492        self.kind.iter().any(|kind| kind == name)
493    }
494
495    /// Return true if this target is of kind "lib".
496    pub fn is_lib(&self) -> bool {
497        self.is_kind("lib")
498    }
499
500    /// Return true if this target is of kind "bin".
501    pub fn is_bin(&self) -> bool {
502        self.is_kind("bin")
503    }
504
505    /// Return true if this target is of kind "example".
506    pub fn is_example(&self) -> bool {
507        self.is_kind("example")
508    }
509
510    /// Return true if this target is of kind "test".
511    pub fn is_test(&self) -> bool {
512        self.is_kind("test")
513    }
514
515    /// Return true if this target is of kind "bench".
516    pub fn is_bench(&self) -> bool {
517        self.is_kind("bench")
518    }
519
520    /// Return true if this target is of kind "custom-build".
521    pub fn is_custom_build(&self) -> bool {
522        self.is_kind("custom-build")
523    }
524}
525
526/// The Rust edition
527///
528/// As of writing this comment rust editions 2024, 2027 and 2030 are not actually a thing yet but are parsed nonetheless for future proofing.
529#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)]
530#[non_exhaustive]
531pub enum Edition {
532    /// Edition 2015
533    #[serde(rename = "2015")]
534    E2015,
535    /// Edition 2018
536    #[serde(rename = "2018")]
537    E2018,
538    /// Edition 2021
539    #[serde(rename = "2021")]
540    E2021,
541    #[doc(hidden)]
542    #[serde(rename = "2024")]
543    _E2024,
544    #[doc(hidden)]
545    #[serde(rename = "2027")]
546    _E2027,
547    #[doc(hidden)]
548    #[serde(rename = "2030")]
549    _E2030,
550}
551
552impl Edition {
553    /// Return the string representation of the edition
554    pub fn as_str(&self) -> &'static str {
555        use Edition::*;
556        match self {
557            E2015 => "2015",
558            E2018 => "2018",
559            E2021 => "2021",
560            _E2024 => "2024",
561            _E2027 => "2027",
562            _E2030 => "2030",
563        }
564    }
565}
566
567impl fmt::Display for Edition {
568    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
569        f.write_str(self.as_str())
570    }
571}
572
573impl Default for Edition {
574    fn default() -> Self {
575        Self::E2015
576    }
577}
578
579fn default_true() -> bool {
580    true
581}
582
583/// Cargo features flags
584#[derive(Debug, Clone)]
585pub enum CargoOpt {
586    /// Run cargo with `--features-all`
587    AllFeatures,
588    /// Run cargo with `--no-default-features`
589    NoDefaultFeatures,
590    /// Run cargo with `--features <FEATURES>`
591    SomeFeatures(Vec<String>),
592}
593
594/// A builder for configurating `cargo metadata` invocation.
595#[derive(Debug, Clone, Default)]
596pub struct MetadataCommand {
597    /// Path to `cargo` executable.  If not set, this will use the
598    /// the `$CARGO` environment variable, and if that is not set, will
599    /// simply be `cargo`.
600    cargo_path: Option<PathBuf>,
601    /// Path to `Cargo.toml`
602    manifest_path: Option<PathBuf>,
603    /// Current directory of the `cargo metadata` process.
604    current_dir: Option<PathBuf>,
605    /// Output information only about workspace members and don't fetch dependencies.
606    no_deps: bool,
607    /// Collections of `CargoOpt::SomeFeatures(..)`
608    features: Vec<String>,
609    /// Latched `CargoOpt::AllFeatures`
610    all_features: bool,
611    /// Latched `CargoOpt::NoDefaultFeatures`
612    no_default_features: bool,
613    /// Arbitrary command line flags to pass to `cargo`.  These will be added
614    /// to the end of the command line invocation.
615    other_options: Vec<String>,
616    /// Arbitrary environment variables to set when running `cargo`.  These will be merged into
617    /// the calling environment, overriding any which clash.
618    env: HashMap<OsString, OsString>,
619    /// Show stderr
620    verbose: bool,
621}
622
623impl MetadataCommand {
624    /// Creates a default `cargo metadata` command, which will look for
625    /// `Cargo.toml` in the ancestors of the current directory.
626    pub fn new() -> MetadataCommand {
627        MetadataCommand::default()
628    }
629    /// Path to `cargo` executable.  If not set, this will use the
630    /// the `$CARGO` environment variable, and if that is not set, will
631    /// simply be `cargo`.
632    pub fn cargo_path(&mut self, path: impl Into<PathBuf>) -> &mut MetadataCommand {
633        self.cargo_path = Some(path.into());
634        self
635    }
636    /// Path to `Cargo.toml`
637    pub fn manifest_path(&mut self, path: impl Into<PathBuf>) -> &mut MetadataCommand {
638        self.manifest_path = Some(path.into());
639        self
640    }
641    /// Current directory of the `cargo metadata` process.
642    pub fn current_dir(&mut self, path: impl Into<PathBuf>) -> &mut MetadataCommand {
643        self.current_dir = Some(path.into());
644        self
645    }
646    /// Output information only about workspace members and don't fetch dependencies.
647    pub fn no_deps(&mut self) -> &mut MetadataCommand {
648        self.no_deps = true;
649        self
650    }
651    /// Which features to include.
652    ///
653    /// Call this multiple times to specify advanced feature configurations:
654    ///
655    /// ```no_run
656    /// # use cargo_metadata::{CargoOpt, MetadataCommand};
657    /// MetadataCommand::new()
658    ///     .features(CargoOpt::NoDefaultFeatures)
659    ///     .features(CargoOpt::SomeFeatures(vec!["feat1".into(), "feat2".into()]))
660    ///     .features(CargoOpt::SomeFeatures(vec!["feat3".into()]))
661    ///     // ...
662    ///     # ;
663    /// ```
664    ///
665    /// # Panics
666    ///
667    /// `cargo metadata` rejects multiple `--no-default-features` flags. Similarly, the `features()`
668    /// method panics when specifying multiple `CargoOpt::NoDefaultFeatures`:
669    ///
670    /// ```should_panic
671    /// # use cargo_metadata::{CargoOpt, MetadataCommand};
672    /// MetadataCommand::new()
673    ///     .features(CargoOpt::NoDefaultFeatures)
674    ///     .features(CargoOpt::NoDefaultFeatures) // <-- panic!
675    ///     // ...
676    ///     # ;
677    /// ```
678    ///
679    /// The method also panics for multiple `CargoOpt::AllFeatures` arguments:
680    ///
681    /// ```should_panic
682    /// # use cargo_metadata::{CargoOpt, MetadataCommand};
683    /// MetadataCommand::new()
684    ///     .features(CargoOpt::AllFeatures)
685    ///     .features(CargoOpt::AllFeatures) // <-- panic!
686    ///     // ...
687    ///     # ;
688    /// ```
689    pub fn features(&mut self, features: CargoOpt) -> &mut MetadataCommand {
690        match features {
691            CargoOpt::SomeFeatures(features) => self.features.extend(features),
692            CargoOpt::NoDefaultFeatures => {
693                assert!(
694                    !self.no_default_features,
695                    "Do not supply CargoOpt::NoDefaultFeatures more than once!"
696                );
697                self.no_default_features = true;
698            }
699            CargoOpt::AllFeatures => {
700                assert!(
701                    !self.all_features,
702                    "Do not supply CargoOpt::AllFeatures more than once!"
703                );
704                self.all_features = true;
705            }
706        }
707        self
708    }
709    /// Arbitrary command line flags to pass to `cargo`.  These will be added
710    /// to the end of the command line invocation.
711    pub fn other_options(&mut self, options: impl Into<Vec<String>>) -> &mut MetadataCommand {
712        self.other_options = options.into();
713        self
714    }
715
716    /// Arbitrary environment variables to set when running `cargo`.  These will be merged into
717    /// the calling environment, overriding any which clash.
718    ///
719    /// Some examples of when you may want to use this:
720    /// 1. Setting cargo config values without needing a .cargo/config.toml file, e.g. to set
721    ///    `CARGO_NET_GIT_FETCH_WITH_CLI=true`
722    /// 2. To specify a custom path to RUSTC if your rust toolchain components aren't laid out in
723    ///    the way cargo expects by default.
724    ///
725    /// ```no_run
726    /// # use cargo_metadata::{CargoOpt, MetadataCommand};
727    /// MetadataCommand::new()
728    ///     .env("CARGO_NET_GIT_FETCH_WITH_CLI", "true")
729    ///     .env("RUSTC", "/path/to/rustc")
730    ///     // ...
731    ///     # ;
732    /// ```
733    pub fn env<K: Into<OsString>, V: Into<OsString>>(
734        &mut self,
735        key: K,
736        val: V,
737    ) -> &mut MetadataCommand {
738        self.env.insert(key.into(), val.into());
739        self
740    }
741
742    /// Set whether to show stderr
743    pub fn verbose(&mut self, verbose: bool) -> &mut MetadataCommand {
744        self.verbose = verbose;
745        self
746    }
747
748    /// Builds a command for `cargo metadata`.  This is the first
749    /// part of the work of `exec`.
750    pub fn cargo_command(&self) -> Command {
751        let cargo = self
752            .cargo_path
753            .clone()
754            .or_else(|| env::var("CARGO").map(PathBuf::from).ok())
755            .unwrap_or_else(|| PathBuf::from("cargo"));
756        let mut cmd = Command::new(cargo);
757        cmd.args(&["metadata", "--format-version", "1"]);
758
759        if self.no_deps {
760            cmd.arg("--no-deps");
761        }
762
763        if let Some(path) = self.current_dir.as_ref() {
764            cmd.current_dir(path);
765        }
766
767        if !self.features.is_empty() {
768            cmd.arg("--features").arg(self.features.join(","));
769        }
770        if self.all_features {
771            cmd.arg("--all-features");
772        }
773        if self.no_default_features {
774            cmd.arg("--no-default-features");
775        }
776
777        if let Some(manifest_path) = &self.manifest_path {
778            cmd.arg("--manifest-path").arg(manifest_path.as_os_str());
779        }
780        cmd.args(&self.other_options);
781
782        cmd.envs(&self.env);
783
784        cmd
785    }
786
787    /// Parses `cargo metadata` output.  `data` must have been
788    /// produced by a command built with `cargo_command`.
789    pub fn parse<T: AsRef<str>>(data: T) -> Result<Metadata> {
790        let meta = serde_json::from_str(data.as_ref())?;
791        Ok(meta)
792    }
793
794    /// Runs configured `cargo metadata` and returns parsed `Metadata`.
795    pub fn exec(&self) -> Result<Metadata> {
796        let mut command = self.cargo_command();
797        if self.verbose {
798            command.stderr(Stdio::inherit());
799        }
800        let output = command.output()?;
801        if !output.status.success() {
802            return Err(Error::CargoMetadata {
803                stderr: String::from_utf8(output.stderr)?,
804            });
805        }
806        let stdout = from_utf8(&output.stdout)?
807            .lines()
808            .find(|line| line.starts_with('{'))
809            .ok_or(Error::NoJson)?;
810        Self::parse(stdout)
811    }
812}