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 valueBTreeMap<&K, &V>
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: &&Package| 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: &Utf8PathBuf| {
402 self.manifest_path
403 .parent()
404 .unwrap_or(&self.manifest_path)
405 .join(path: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: &Utf8PathBuf| {
412 self.manifest_path
413 .parent()
414 .unwrap_or(&self.manifest_path)
415 .join(path: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}
813