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