cargo_metadata/
lib.rs

1#![cfg_attr(docsrs, feature(doc_auto_cfg))]
2#![deny(missing_docs)]
3//! Structured access to the output of `cargo metadata` and `cargo --message-format=json`.
4//! Usually used from within a `cargo-*` executable
5//!
6//! See the [cargo book](https://doc.rust-lang.org/cargo/index.html) for
7//! details on cargo itself.
8//!
9//! ## Examples
10//!
11//! Get the current crate's metadata without default features but with all dependency information.
12//!
13//! ```rust
14//! # use std::path::Path;
15//! # use cargo_metadata::{MetadataCommand, CargoOpt};
16//! let _metadata = MetadataCommand::new().exec().unwrap();
17//! ```
18//!
19//!
20//! If you have a program that takes `--manifest-path` as an argument, you can forward that
21//! to [MetadataCommand]:
22//!
23//! ```rust
24//! # use cargo_metadata::MetadataCommand;
25//! # use std::path::Path;
26//! let mut args = std::env::args().skip_while(|val| !val.starts_with("--manifest-path"));
27//! let mut cmd = MetadataCommand::new();
28//! let manifest_path = match args.next() {
29//!     Some(ref p) if p == "--manifest-path" => {
30//!         cmd.manifest_path(args.next().unwrap());
31//!     }
32//!     Some(p) => {
33//!         cmd.manifest_path(p.trim_start_matches("--manifest-path="));
34//!     }
35//!     None => {}
36//! };
37//!
38//! let _metadata = cmd.exec().unwrap();
39//! ```
40//!
41//! Pass features flags, e.g. `--all-features`.
42//!
43//! ```rust
44//! # use std::path::Path;
45//! # use cargo_metadata::{MetadataCommand, CargoOpt};
46//! let _metadata = MetadataCommand::new()
47//!     .manifest_path("./Cargo.toml")
48//!     .features(CargoOpt::AllFeatures)
49//!     .exec()
50//!     .unwrap();
51//! ```
52//!
53//! Parse message-format output produced by other cargo commands.
54//! It is recommended to use crates like `escargot` to produce the [Command].
55//!
56//! ```
57//! # use std::process::{Stdio, Command};
58//! # use cargo_metadata::Message;
59//! let mut command = Command::new("cargo")
60//!     .args(&["build", "--message-format=json-render-diagnostics"])
61//!     .stdout(Stdio::piped())
62//!     .spawn()
63//!     .unwrap();
64//!
65//! let reader = std::io::BufReader::new(command.stdout.take().unwrap());
66//! for message in cargo_metadata::Message::parse_stream(reader) {
67//!     match message.unwrap() {
68//!         Message::CompilerMessage(msg) => {
69//!             println!("{:?}", msg);
70//!         },
71//!         Message::CompilerArtifact(artifact) => {
72//!             println!("{:?}", artifact);
73//!         },
74//!         Message::BuildScriptExecuted(script) => {
75//!             println!("{:?}", script);
76//!         },
77//!         Message::BuildFinished(finished) => {
78//!             println!("{:?}", finished);
79//!         },
80//!         _ => () // Unknown message
81//!     }
82//! }
83//!
84//! let output = command.wait().expect("Couldn't get cargo's exit status");
85//! ```
86
87use camino::Utf8PathBuf;
88#[cfg(feature = "builder")]
89use derive_builder::Builder;
90use std::collections::BTreeMap;
91use std::env;
92use std::ffi::OsString;
93use std::fmt;
94use std::hash::Hash;
95use std::path::PathBuf;
96use std::process::{Command, Stdio};
97use std::str::{from_utf8, FromStr};
98
99pub use camino;
100pub use semver;
101use semver::Version;
102
103use cargo_util_schemas::manifest::{FeatureName, PackageName};
104#[cfg(feature = "builder")]
105pub use dependency::DependencyBuilder;
106pub use dependency::{Dependency, DependencyKind};
107use diagnostic::Diagnostic;
108pub use errors::{Error, Result};
109#[cfg(feature = "unstable")]
110pub use libtest::TestMessage;
111#[allow(deprecated)]
112pub use messages::parse_messages;
113pub use messages::{
114    Artifact, ArtifactDebuginfo, ArtifactProfile, BuildFinished, BuildScript, CompilerMessage,
115    Message, MessageIter,
116};
117#[cfg(feature = "builder")]
118pub use messages::{
119    ArtifactBuilder, ArtifactProfileBuilder, BuildFinishedBuilder, BuildScriptBuilder,
120    CompilerMessageBuilder,
121};
122use serde::{Deserialize, Deserializer, Serialize};
123
124mod dependency;
125pub mod diagnostic;
126mod errors;
127#[cfg(feature = "unstable")]
128pub mod libtest;
129mod messages;
130
131/// An "opaque" identifier for a package.
132///
133/// It is possible to inspect the `repr` field, if the need arises, but its
134/// precise format is an implementation detail and is subject to change.
135///
136/// `Metadata` can be indexed by `PackageId`.
137#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
138#[serde(transparent)]
139pub struct PackageId {
140    /// The underlying string representation of id.
141    pub repr: String,
142}
143
144impl fmt::Display for PackageId {
145    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
146        fmt::Display::fmt(&self.repr, f)
147    }
148}
149
150/// Helpers for default metadata fields
151fn is_null(value: &serde_json::Value) -> bool {
152    matches!(value, serde_json::Value::Null)
153}
154
155#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, Hash)]
156#[cfg_attr(feature = "builder", derive(Builder))]
157#[non_exhaustive]
158#[cfg_attr(feature = "builder", builder(pattern = "owned", setter(into)))]
159/// Starting point for metadata returned by `cargo metadata`
160pub struct Metadata {
161    /// A list of all crates referenced by this crate (and the crate itself)
162    pub packages: Vec<Package>,
163    /// A list of all workspace members
164    pub workspace_members: Vec<PackageId>,
165    /// The list of default workspace members
166    ///
167    /// This is not available if running with a version of Cargo older than 1.71.
168    ///
169    /// You can check whether it is available or missing using respectively
170    /// [`WorkspaceDefaultMembers::is_available`] and [`WorkspaceDefaultMembers::is_missing`].
171    #[serde(default, skip_serializing_if = "WorkspaceDefaultMembers::is_missing")]
172    pub workspace_default_members: WorkspaceDefaultMembers,
173    /// Dependencies graph
174    pub resolve: Option<Resolve>,
175    /// Workspace root
176    pub workspace_root: Utf8PathBuf,
177    /// Target directory
178    pub target_directory: Utf8PathBuf,
179    /// Build directory
180    ///
181    /// Only populated if `-Zbuild-dir` is passed via .other_options()
182    // TODO: This should become non optional once cargo build-dir is stablized: https://github.com/rust-lang/cargo/issues/14125
183    #[cfg(feature = "unstable")]
184    pub build_directory: Option<Utf8PathBuf>,
185    /// The workspace-level metadata object. Null if non-existent.
186    #[serde(rename = "metadata", default, skip_serializing_if = "is_null")]
187    pub workspace_metadata: serde_json::Value,
188    /// The metadata format version
189    version: usize,
190}
191
192impl Metadata {
193    /// Get the workspace's root package of this metadata instance.
194    pub fn root_package(&self) -> Option<&Package> {
195        match &self.resolve {
196            Some(resolve) => {
197                // if dependencies are resolved, use Cargo's answer
198                let root = resolve.root.as_ref()?;
199                self.packages.iter().find(|pkg| &pkg.id == root)
200            }
201            None => {
202                // if dependencies aren't resolved, check for a root package manually
203                let root_manifest_path = self.workspace_root.join("Cargo.toml");
204                self.packages
205                    .iter()
206                    .find(|pkg| pkg.manifest_path == root_manifest_path)
207            }
208        }
209    }
210
211    /// Get the workspace packages.
212    pub fn workspace_packages(&self) -> Vec<&Package> {
213        self.packages
214            .iter()
215            .filter(|&p| self.workspace_members.contains(&p.id))
216            .collect()
217    }
218
219    /// Get the workspace default packages.
220    ///
221    /// # Panics
222    ///
223    /// This will panic if running with a version of Cargo older than 1.71.
224    pub fn workspace_default_packages(&self) -> Vec<&Package> {
225        self.packages
226            .iter()
227            .filter(|&p| self.workspace_default_members.contains(&p.id))
228            .collect()
229    }
230}
231
232impl<'a> std::ops::Index<&'a PackageId> for Metadata {
233    type Output = Package;
234
235    fn index(&self, idx: &'a PackageId) -> &Self::Output {
236        self.packages
237            .iter()
238            .find(|p| p.id == *idx)
239            .unwrap_or_else(|| panic!("no package with this id: {idx:?}"))
240    }
241}
242
243#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Hash, Default)]
244#[serde(transparent)]
245/// A list of default workspace members.
246///
247/// See [`Metadata::workspace_default_members`].
248///
249/// It is only available if running a version of Cargo of 1.71 or newer.
250///
251/// # Panics
252///
253/// Dereferencing when running an older version of Cargo will panic.
254pub struct WorkspaceDefaultMembers(Option<Vec<PackageId>>);
255
256impl WorkspaceDefaultMembers {
257    /// Return `true` if the list of workspace default members is supported by
258    /// the called cargo-metadata version and `false` otherwise.
259    ///
260    /// In particular useful when parsing the output of `cargo-metadata` for
261    /// versions of Cargo < 1.71, as dereferencing [`WorkspaceDefaultMembers`]
262    /// for these versions will panic.
263    ///
264    /// Opposite of [`WorkspaceDefaultMembers::is_missing`].
265    pub fn is_available(&self) -> bool {
266        self.0.is_some()
267    }
268
269    /// Return `false` if the list of workspace default members is supported by
270    /// the called cargo-metadata version and `true` otherwise.
271    ///
272    /// In particular useful when parsing the output of `cargo-metadata` for
273    /// versions of Cargo < 1.71, as dereferencing [`WorkspaceDefaultMembers`]
274    /// for these versions will panic.
275    ///
276    /// Opposite of [`WorkspaceDefaultMembers::is_available`].
277    pub fn is_missing(&self) -> bool {
278        self.0.is_none()
279    }
280}
281
282impl core::ops::Deref for WorkspaceDefaultMembers {
283    type Target = [PackageId];
284
285    fn deref(&self) -> &Self::Target {
286        self.0
287            .as_ref()
288            .expect("WorkspaceDefaultMembers should only be dereferenced on Cargo versions >= 1.71")
289    }
290}
291
292#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, Hash)]
293#[cfg_attr(feature = "builder", derive(Builder))]
294#[non_exhaustive]
295#[cfg_attr(feature = "builder", builder(pattern = "owned", setter(into)))]
296/// A dependency graph
297pub struct Resolve {
298    /// Nodes in a dependencies graph
299    pub nodes: Vec<Node>,
300
301    /// The crate for which the metadata was read.
302    pub root: Option<PackageId>,
303}
304
305impl<'a> std::ops::Index<&'a PackageId> for Resolve {
306    type Output = Node;
307
308    fn index(&self, idx: &'a PackageId) -> &Self::Output {
309        self.nodes
310            .iter()
311            .find(|p| p.id == *idx)
312            .unwrap_or_else(|| panic!("no Node with this id: {idx:?}"))
313    }
314}
315
316#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, Hash)]
317#[cfg_attr(feature = "builder", derive(Builder))]
318#[non_exhaustive]
319#[cfg_attr(feature = "builder", builder(pattern = "owned", setter(into)))]
320/// A node in a dependencies graph
321pub struct Node {
322    /// An opaque identifier for a package
323    pub id: PackageId,
324    /// Dependencies in a structured format.
325    ///
326    /// `deps` handles renamed dependencies whereas `dependencies` does not.
327    #[serde(default)]
328    pub deps: Vec<NodeDep>,
329
330    /// List of opaque identifiers for this node's dependencies.
331    /// It doesn't support renamed dependencies. See `deps`.
332    pub dependencies: Vec<PackageId>,
333
334    /// Features enabled on the crate
335    #[serde(default)]
336    pub features: Vec<FeatureName>,
337}
338
339#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, Hash)]
340#[cfg_attr(feature = "builder", derive(Builder))]
341#[non_exhaustive]
342#[cfg_attr(feature = "builder", builder(pattern = "owned", setter(into)))]
343/// A dependency in a node
344pub struct NodeDep {
345    /// The name of the dependency's library target.
346    /// If the crate was renamed, it is the new name.
347    ///
348    /// If -Zbindeps is enabled local references may result in an empty
349    /// string.
350    ///
351    /// After -Zbindeps gets stabilized, cargo has indicated this field
352    /// will become deprecated.
353    pub name: String,
354    /// Package ID (opaque unique identifier)
355    pub pkg: PackageId,
356    /// The kinds of dependencies.
357    ///
358    /// This field was added in Rust 1.41.
359    #[serde(default)]
360    pub dep_kinds: Vec<DepKindInfo>,
361}
362
363#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, Hash)]
364#[cfg_attr(feature = "builder", derive(Builder))]
365#[non_exhaustive]
366#[cfg_attr(feature = "builder", builder(pattern = "owned", setter(into)))]
367/// Information about a dependency kind.
368pub struct DepKindInfo {
369    /// The kind of dependency.
370    #[serde(deserialize_with = "dependency::parse_dependency_kind")]
371    pub kind: DependencyKind,
372    /// The target platform for the dependency.
373    ///
374    /// This is `None` if it is not a target dependency.
375    ///
376    /// Use the [`Display`] trait to access the contents.
377    ///
378    /// By default all platform dependencies are included in the resolve
379    /// graph. Use Cargo's `--filter-platform` flag if you only want to
380    /// include dependencies for a specific platform.
381    ///
382    /// [`Display`]: std::fmt::Display
383    pub target: Option<dependency::Platform>,
384}
385
386#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, Hash)]
387#[cfg_attr(feature = "builder", derive(Builder))]
388#[non_exhaustive]
389#[cfg_attr(feature = "builder", builder(pattern = "owned", setter(into)))]
390/// One or more crates described by a single `Cargo.toml`
391///
392/// Each [`target`][Package::targets] of a `Package` will be built as a crate.
393/// For more information, see <https://doc.rust-lang.org/book/ch07-01-packages-and-crates.html>.
394pub struct Package {
395    /// The [`name` field](https://doc.rust-lang.org/cargo/reference/manifest.html#the-name-field) as given in the `Cargo.toml`
396    // (We say "given in" instead of "specified in" since the `name` key cannot be inherited from the workspace.)
397    pub name: PackageName,
398    /// The [`version` field](https://doc.rust-lang.org/cargo/reference/manifest.html#the-version-field) as specified in the `Cargo.toml`
399    pub version: Version,
400    /// The [`authors` field](https://doc.rust-lang.org/cargo/reference/manifest.html#the-authors-field) as specified in the `Cargo.toml`
401    #[serde(default)]
402    #[cfg_attr(feature = "builder", builder(default))]
403    pub authors: Vec<String>,
404    /// An opaque identifier for a package
405    pub id: PackageId,
406    /// The source of the package, e.g.
407    /// crates.io or `None` for local projects.
408    // Note that this is NOT the same as cargo_util_schemas::RegistryName
409    #[cfg_attr(feature = "builder", builder(default))]
410    pub source: Option<Source>,
411    /// The [`description` field](https://doc.rust-lang.org/cargo/reference/manifest.html#the-description-field) as specified in the `Cargo.toml`
412    #[cfg_attr(feature = "builder", builder(default))]
413    pub description: Option<String>,
414    /// List of dependencies of this particular package
415    #[cfg_attr(feature = "builder", builder(default))]
416    pub dependencies: Vec<Dependency>,
417    /// The [`license` field](https://doc.rust-lang.org/cargo/reference/manifest.html#the-license-and-license-file-fields) as specified in the `Cargo.toml`
418    #[cfg_attr(feature = "builder", builder(default))]
419    pub license: Option<String>,
420    /// 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`.
421    /// If the package is using a nonstandard license, this key may be specified instead of
422    /// `license`, and must point to a file relative to the manifest.
423    #[cfg_attr(feature = "builder", builder(default))]
424    pub license_file: Option<Utf8PathBuf>,
425    /// Targets provided by the crate (lib, bin, example, test, ...)
426    #[cfg_attr(feature = "builder", builder(default))]
427    pub targets: Vec<Target>,
428    /// Features provided by the crate, mapped to the features required by that feature.
429    #[cfg_attr(feature = "builder", builder(default))]
430    pub features: BTreeMap<String, Vec<String>>,
431    /// Path containing the `Cargo.toml`
432    pub manifest_path: Utf8PathBuf,
433    /// The [`categories` field](https://doc.rust-lang.org/cargo/reference/manifest.html#the-categories-field) as specified in the `Cargo.toml`
434    #[serde(default)]
435    #[cfg_attr(feature = "builder", builder(default))]
436    pub categories: Vec<String>,
437    /// The [`keywords` field](https://doc.rust-lang.org/cargo/reference/manifest.html#the-keywords-field) as specified in the `Cargo.toml`
438    #[serde(default)]
439    #[cfg_attr(feature = "builder", builder(default))]
440    pub keywords: Vec<String>,
441    /// The [`readme` field](https://doc.rust-lang.org/cargo/reference/manifest.html#the-readme-field) as specified in the `Cargo.toml`
442    #[cfg_attr(feature = "builder", builder(default))]
443    pub readme: Option<Utf8PathBuf>,
444    /// The [`repository` URL](https://doc.rust-lang.org/cargo/reference/manifest.html#the-repository-field) as specified in the `Cargo.toml`
445    // can't use `url::Url` because that requires a more recent stable compiler
446    #[cfg_attr(feature = "builder", builder(default))]
447    pub repository: Option<String>,
448    /// The [`homepage` URL](https://doc.rust-lang.org/cargo/reference/manifest.html#the-homepage-field) as specified in the `Cargo.toml`.
449    ///
450    /// On versions of cargo before 1.49, this will always be [`None`].
451    #[cfg_attr(feature = "builder", builder(default))]
452    pub homepage: Option<String>,
453    /// The [`documentation` URL](https://doc.rust-lang.org/cargo/reference/manifest.html#the-documentation-field) as specified in the `Cargo.toml`.
454    ///
455    /// On versions of cargo before 1.49, this will always be [`None`].
456    #[cfg_attr(feature = "builder", builder(default))]
457    pub documentation: Option<String>,
458    /// 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)
459    /// or defaulting to [`Edition::E2015`]).
460    ///
461    /// Beware that individual targets may specify their own edition in
462    /// [`Target::edition`].
463    #[serde(default)]
464    #[cfg_attr(feature = "builder", builder(default))]
465    pub edition: Edition,
466    /// Contents of the free form [`package.metadata` section](https://doc.rust-lang.org/cargo/reference/manifest.html#the-metadata-table).
467    ///
468    /// This contents can be serialized to a struct using serde:
469    ///
470    /// ```rust
471    /// use serde::Deserialize;
472    /// use serde_json::json;
473    ///
474    /// #[derive(Debug, Deserialize)]
475    /// struct SomePackageMetadata {
476    ///     some_value: i32,
477    /// }
478    ///
479    /// let value = json!({
480    ///     "some_value": 42,
481    /// });
482    ///
483    /// let package_metadata: SomePackageMetadata = serde_json::from_value(value).unwrap();
484    /// assert_eq!(package_metadata.some_value, 42);
485    ///
486    /// ```
487    #[serde(default, skip_serializing_if = "is_null")]
488    #[cfg_attr(feature = "builder", builder(default))]
489    pub metadata: serde_json::Value,
490    /// The name of a native library the package is linking to.
491    #[cfg_attr(feature = "builder", builder(default))]
492    pub links: Option<String>,
493    /// 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)).
494    ///
495    /// Publishing is unrestricted if `None`, and forbidden if the `Vec` is empty.
496    ///
497    /// This is always `None` if running with a version of Cargo older than 1.39.
498    #[cfg_attr(feature = "builder", builder(default))]
499    pub publish: Option<Vec<String>>,
500    /// The [`default-run` field](https://doc.rust-lang.org/cargo/reference/manifest.html#the-default-run-field) as given in the `Cargo.toml`
501    // (We say "given in" instead of "specified in" since the `default-run` key cannot be inherited from the workspace.)
502    /// The default binary to run by `cargo run`.
503    ///
504    /// This is always `None` if running with a version of Cargo older than 1.55.
505    #[cfg_attr(feature = "builder", builder(default))]
506    pub default_run: Option<String>,
507    /// The [`rust-version` field](https://doc.rust-lang.org/cargo/reference/manifest.html#the-rust-version-field) as specified in the `Cargo.toml`.
508    /// The minimum supported Rust version of this package.
509    ///
510    /// This is always `None` if running with a version of Cargo older than 1.58.
511    #[serde(default)]
512    #[serde(deserialize_with = "deserialize_rust_version")]
513    #[cfg_attr(feature = "builder", builder(default))]
514    pub rust_version: Option<Version>,
515}
516
517#[cfg(feature = "builder")]
518impl PackageBuilder {
519    /// Construct a new `PackageBuilder` with all required fields.
520    pub fn new(
521        name: impl Into<PackageName>,
522        version: impl Into<Version>,
523        id: impl Into<PackageId>,
524        path: impl Into<Utf8PathBuf>,
525    ) -> Self {
526        Self::default()
527            .name(name)
528            .version(version)
529            .id(id)
530            .manifest_path(path)
531    }
532}
533
534impl Package {
535    /// Full path to the license file if one is present in the manifest
536    pub fn license_file(&self) -> Option<Utf8PathBuf> {
537        self.license_file.as_ref().map(|file| {
538            self.manifest_path
539                .parent()
540                .unwrap_or(&self.manifest_path)
541                .join(file)
542        })
543    }
544
545    /// Full path to the readme file if one is present in the manifest
546    pub fn readme(&self) -> Option<Utf8PathBuf> {
547        self.readme.as_ref().map(|file| {
548            self.manifest_path
549                .parent()
550                .unwrap_or(&self.manifest_path)
551                .join(file)
552        })
553    }
554}
555
556/// The source of a package such as crates.io.
557///
558/// It is possible to inspect the `repr` field, if the need arises, but its
559/// precise format is an implementation detail and is subject to change.
560#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, Hash)]
561#[serde(transparent)]
562pub struct Source {
563    /// The underlying string representation of a source.
564    pub repr: String,
565}
566
567impl Source {
568    /// Returns true if the source is crates.io.
569    pub fn is_crates_io(&self) -> bool {
570        self.repr == "registry+https://github.com/rust-lang/crates.io-index"
571    }
572}
573
574impl fmt::Display for Source {
575    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
576        fmt::Display::fmt(&self.repr, f)
577    }
578}
579
580#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, Hash)]
581#[cfg_attr(feature = "builder", derive(Builder))]
582#[cfg_attr(feature = "builder", builder(pattern = "owned", setter(into)))]
583#[non_exhaustive]
584/// A single target (lib, bin, example, ...) provided by a crate
585pub struct Target {
586    /// Name as given in the `Cargo.toml` or generated from the file name
587    pub name: String,
588    /// Kind of target.
589    ///
590    /// The possible values are `example`, `test`, `bench`, `custom-build` and
591    /// [Cargo crate types](https://doc.rust-lang.org/cargo/reference/cargo-targets.html#the-crate-type-field):
592    /// `bin`, `lib`, `rlib`, `dylib`, `cdylib`, `staticlib`, `proc-macro`.
593    ///
594    /// Other possible values may be added in the future.
595    pub kind: Vec<TargetKind>,
596    /// Similar to `kind`, but only reports the
597    /// [Cargo crate types](https://doc.rust-lang.org/cargo/reference/cargo-targets.html#the-crate-type-field):
598    /// `bin`, `lib`, `rlib`, `dylib`, `cdylib`, `staticlib`, `proc-macro`.
599    /// Everything that's not a proc macro or a library of some kind is reported as "bin".
600    ///
601    /// Other possible values may be added in the future.
602    #[serde(default)]
603    #[cfg_attr(feature = "builder", builder(default))]
604    pub crate_types: Vec<CrateType>,
605
606    #[serde(default)]
607    #[cfg_attr(feature = "builder", builder(default))]
608    #[serde(rename = "required-features")]
609    /// This target is built only if these features are enabled.
610    /// It doesn't apply to `lib` targets.
611    pub required_features: Vec<String>,
612    /// Path to the main source file of the target
613    pub src_path: Utf8PathBuf,
614    /// Rust edition for this target
615    #[serde(default)]
616    #[cfg_attr(feature = "builder", builder(default))]
617    pub edition: Edition,
618    /// Whether or not this target has doc tests enabled, and the target is
619    /// compatible with doc testing.
620    ///
621    /// This is always `true` if running with a version of Cargo older than 1.37.
622    #[serde(default = "default_true")]
623    #[cfg_attr(feature = "builder", builder(default = "true"))]
624    pub doctest: bool,
625    /// Whether or not this target is tested by default by `cargo test`.
626    ///
627    /// This is always `true` if running with a version of Cargo older than 1.47.
628    #[serde(default = "default_true")]
629    #[cfg_attr(feature = "builder", builder(default = "true"))]
630    pub test: bool,
631    /// Whether or not this target is documented by `cargo doc`.
632    ///
633    /// This is always `true` if running with a version of Cargo older than 1.50.
634    #[serde(default = "default_true")]
635    #[cfg_attr(feature = "builder", builder(default = "true"))]
636    pub doc: bool,
637}
638
639macro_rules! methods_target_is_kind {
640    ($($name:ident => $kind:expr),*) => {
641        $(
642            /// Return true if this target is of kind `$kind`.
643            pub fn $name(&self) -> bool {
644                self.is_kind($kind)
645            }
646        )*
647    }
648}
649
650impl Target {
651    /// Return true if this target is of the given kind.
652    pub fn is_kind(&self, name: TargetKind) -> bool {
653        self.kind.iter().any(|kind| kind == &name)
654    }
655
656    // Generate `is_*` methods for each `TargetKind`
657    methods_target_is_kind! {
658        is_lib => TargetKind::Lib,
659        is_bin => TargetKind::Bin,
660        is_example => TargetKind::Example,
661        is_test => TargetKind::Test,
662        is_bench => TargetKind::Bench,
663        is_custom_build => TargetKind::CustomBuild,
664        is_proc_macro => TargetKind::ProcMacro,
665        is_cdylib => TargetKind::CDyLib,
666        is_dylib => TargetKind::DyLib,
667        is_rlib => TargetKind::RLib,
668        is_staticlib => TargetKind::StaticLib
669    }
670}
671
672/// Kind of target.
673///
674/// The possible values are `example`, `test`, `bench`, `custom-build` and
675/// [Cargo crate types](https://doc.rust-lang.org/cargo/reference/cargo-targets.html#the-crate-type-field):
676/// `bin`, `lib`, `rlib`, `dylib`, `cdylib`, `staticlib`, `proc-macro`.
677///
678/// Other possible values may be added in the future.
679#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
680#[non_exhaustive]
681pub enum TargetKind {
682    /// `cargo bench` target
683    #[serde(rename = "bench")]
684    Bench,
685    /// Binary executable target
686    #[serde(rename = "bin")]
687    Bin,
688    /// Custom build target
689    #[serde(rename = "custom-build")]
690    CustomBuild,
691    /// Dynamic system library target
692    #[serde(rename = "cdylib")]
693    CDyLib,
694    /// Dynamic Rust library target
695    #[serde(rename = "dylib")]
696    DyLib,
697    /// Example target
698    #[serde(rename = "example")]
699    Example,
700    /// Rust library
701    #[serde(rename = "lib")]
702    Lib,
703    /// Procedural Macro
704    #[serde(rename = "proc-macro")]
705    ProcMacro,
706    /// Rust library for use as an intermediate artifact
707    #[serde(rename = "rlib")]
708    RLib,
709    /// Static system library
710    #[serde(rename = "staticlib")]
711    StaticLib,
712    /// Test target
713    #[serde(rename = "test")]
714    Test,
715    /// Unknown type
716    #[serde(untagged)]
717    Unknown(String),
718}
719
720impl From<&str> for TargetKind {
721    fn from(value: &str) -> Self {
722        match value {
723            "example" => TargetKind::Example,
724            "test" => TargetKind::Test,
725            "bench" => TargetKind::Bench,
726            "custom-build" => TargetKind::CustomBuild,
727            "bin" => TargetKind::Bin,
728            "lib" => TargetKind::Lib,
729            "rlib" => TargetKind::RLib,
730            "dylib" => TargetKind::DyLib,
731            "cdylib" => TargetKind::CDyLib,
732            "staticlib" => TargetKind::StaticLib,
733            "proc-macro" => TargetKind::ProcMacro,
734            x => TargetKind::Unknown(x.to_string()),
735        }
736    }
737}
738
739impl FromStr for TargetKind {
740    type Err = std::convert::Infallible;
741
742    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
743        Ok(TargetKind::from(s))
744    }
745}
746
747impl fmt::Display for TargetKind {
748    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
749        match self {
750            Self::Bench => "bench".fmt(f),
751            Self::Bin => "bin".fmt(f),
752            Self::CustomBuild => "custom-build".fmt(f),
753            Self::CDyLib => "cdylib".fmt(f),
754            Self::DyLib => "dylib".fmt(f),
755            Self::Example => "example".fmt(f),
756            Self::Lib => "lib".fmt(f),
757            Self::ProcMacro => "proc-macro".fmt(f),
758            Self::RLib => "rlib".fmt(f),
759            Self::StaticLib => "staticlib".fmt(f),
760            Self::Test => "test".fmt(f),
761            Self::Unknown(x) => x.fmt(f),
762        }
763    }
764}
765
766/// Similar to `kind`, but only reports the
767/// [Cargo crate types](https://doc.rust-lang.org/cargo/reference/cargo-targets.html#the-crate-type-field):
768///
769/// `bin`, `lib`, `rlib`, `dylib`, `cdylib`, `staticlib`, `proc-macro`.
770/// Everything that's not a proc macro or a library of some kind is reported as "bin".
771///
772/// Other possible values may be added in the future.
773#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
774#[non_exhaustive]
775pub enum CrateType {
776    /// Binary executable target
777    #[serde(rename = "bin")]
778    Bin,
779    /// Dynamic system library target
780    #[serde(rename = "cdylib")]
781    CDyLib,
782    /// Dynamic Rust library target
783    #[serde(rename = "dylib")]
784    DyLib,
785    /// Rust library
786    #[serde(rename = "lib")]
787    Lib,
788    /// Procedural Macro
789    #[serde(rename = "proc-macro")]
790    ProcMacro,
791    /// Rust library for use as an intermediate artifact
792    #[serde(rename = "rlib")]
793    RLib,
794    /// Static system library
795    #[serde(rename = "staticlib")]
796    StaticLib,
797    /// Unkown type
798    #[serde(untagged)]
799    Unknown(String),
800}
801
802impl From<&str> for CrateType {
803    fn from(value: &str) -> Self {
804        match value {
805            "bin" => CrateType::Bin,
806            "lib" => CrateType::Lib,
807            "rlib" => CrateType::RLib,
808            "dylib" => CrateType::DyLib,
809            "cdylib" => CrateType::CDyLib,
810            "staticlib" => CrateType::StaticLib,
811            "proc-macro" => CrateType::ProcMacro,
812            x => CrateType::Unknown(x.to_string()),
813        }
814    }
815}
816
817impl FromStr for CrateType {
818    type Err = std::convert::Infallible;
819
820    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
821        Ok(CrateType::from(s))
822    }
823}
824
825impl fmt::Display for CrateType {
826    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
827        match self {
828            Self::Bin => "bin".fmt(f),
829            Self::CDyLib => "cdylib".fmt(f),
830            Self::DyLib => "dylib".fmt(f),
831            Self::Lib => "lib".fmt(f),
832            Self::ProcMacro => "proc-macro".fmt(f),
833            Self::RLib => "rlib".fmt(f),
834            Self::StaticLib => "staticlib".fmt(f),
835            Self::Unknown(x) => x.fmt(f),
836        }
837    }
838}
839
840/// The Rust edition
841///
842/// As of writing this comment rust editions 2027 and 2030 are not actually a thing yet but are parsed nonetheless for future proofing.
843#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash, PartialOrd, Ord)]
844#[non_exhaustive]
845pub enum Edition {
846    /// Edition 2015
847    #[serde(rename = "2015")]
848    E2015,
849    /// Edition 2018
850    #[serde(rename = "2018")]
851    E2018,
852    /// Edition 2021
853    #[serde(rename = "2021")]
854    E2021,
855    /// Edition 2024
856    #[serde(rename = "2024")]
857    E2024,
858    #[doc(hidden)]
859    #[serde(rename = "2027")]
860    _E2027,
861    #[doc(hidden)]
862    #[serde(rename = "2030")]
863    _E2030,
864}
865
866impl Edition {
867    /// Return the string representation of the edition
868    pub fn as_str(&self) -> &'static str {
869        use Edition::*;
870        match self {
871            E2015 => "2015",
872            E2018 => "2018",
873            E2021 => "2021",
874            E2024 => "2024",
875            _E2027 => "2027",
876            _E2030 => "2030",
877        }
878    }
879}
880
881impl fmt::Display for Edition {
882    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
883        f.write_str(self.as_str())
884    }
885}
886
887impl Default for Edition {
888    fn default() -> Self {
889        Self::E2015
890    }
891}
892
893fn default_true() -> bool {
894    true
895}
896
897/// Cargo features flags
898#[derive(Debug, Clone)]
899pub enum CargoOpt {
900    /// Run cargo with `--features-all`
901    AllFeatures,
902    /// Run cargo with `--no-default-features`
903    NoDefaultFeatures,
904    /// Run cargo with `--features <FEATURES>`
905    SomeFeatures(Vec<String>),
906}
907
908/// A builder for configuring `cargo metadata` invocation.
909#[derive(Debug, Clone, Default)]
910pub struct MetadataCommand {
911    /// Path to `cargo` executable.  If not set, this will use the
912    /// the `$CARGO` environment variable, and if that is not set, will
913    /// simply be `cargo`.
914    cargo_path: Option<PathBuf>,
915    /// Path to `Cargo.toml`
916    manifest_path: Option<PathBuf>,
917    /// Current directory of the `cargo metadata` process.
918    current_dir: Option<PathBuf>,
919    /// Output information only about workspace members and don't fetch dependencies.
920    no_deps: bool,
921    /// Collections of `CargoOpt::SomeFeatures(..)`
922    features: Vec<String>,
923    /// Latched `CargoOpt::AllFeatures`
924    all_features: bool,
925    /// Latched `CargoOpt::NoDefaultFeatures`
926    no_default_features: bool,
927    /// Arbitrary command line flags to pass to `cargo`. These will be added
928    /// to the end of the command line invocation.
929    other_options: Vec<String>,
930    /// Arbitrary environment variables to set or remove (depending on
931    /// [`Option`] value) when running `cargo`. These will be merged into the
932    /// calling environment, overriding any which clash.
933    env: BTreeMap<OsString, Option<OsString>>,
934    /// Show stderr
935    verbose: bool,
936}
937
938impl MetadataCommand {
939    /// Creates a default `cargo metadata` command, which will look for
940    /// `Cargo.toml` in the ancestors of the current directory.
941    pub fn new() -> MetadataCommand {
942        MetadataCommand::default()
943    }
944    /// Path to `cargo` executable.  If not set, this will use the
945    /// the `$CARGO` environment variable, and if that is not set, will
946    /// simply be `cargo`.
947    pub fn cargo_path(&mut self, path: impl Into<PathBuf>) -> &mut MetadataCommand {
948        self.cargo_path = Some(path.into());
949        self
950    }
951    /// Path to `Cargo.toml`
952    pub fn manifest_path(&mut self, path: impl Into<PathBuf>) -> &mut MetadataCommand {
953        self.manifest_path = Some(path.into());
954        self
955    }
956    /// Current directory of the `cargo metadata` process.
957    pub fn current_dir(&mut self, path: impl Into<PathBuf>) -> &mut MetadataCommand {
958        self.current_dir = Some(path.into());
959        self
960    }
961    /// Output information only about workspace members and don't fetch dependencies.
962    pub fn no_deps(&mut self) -> &mut MetadataCommand {
963        self.no_deps = true;
964        self
965    }
966    /// Which features to include.
967    ///
968    /// Call this multiple times to specify advanced feature configurations:
969    ///
970    /// ```no_run
971    /// # use cargo_metadata::{CargoOpt, MetadataCommand};
972    /// MetadataCommand::new()
973    ///     .features(CargoOpt::NoDefaultFeatures)
974    ///     .features(CargoOpt::SomeFeatures(vec!["feat1".into(), "feat2".into()]))
975    ///     .features(CargoOpt::SomeFeatures(vec!["feat3".into()]))
976    ///     // ...
977    ///     # ;
978    /// ```
979    ///
980    /// # Panics
981    ///
982    /// `cargo metadata` rejects multiple `--no-default-features` flags. Similarly, the `features()`
983    /// method panics when specifying multiple `CargoOpt::NoDefaultFeatures`:
984    ///
985    /// ```should_panic
986    /// # use cargo_metadata::{CargoOpt, MetadataCommand};
987    /// MetadataCommand::new()
988    ///     .features(CargoOpt::NoDefaultFeatures)
989    ///     .features(CargoOpt::NoDefaultFeatures) // <-- panic!
990    ///     // ...
991    ///     # ;
992    /// ```
993    ///
994    /// The method also panics for multiple `CargoOpt::AllFeatures` arguments:
995    ///
996    /// ```should_panic
997    /// # use cargo_metadata::{CargoOpt, MetadataCommand};
998    /// MetadataCommand::new()
999    ///     .features(CargoOpt::AllFeatures)
1000    ///     .features(CargoOpt::AllFeatures) // <-- panic!
1001    ///     // ...
1002    ///     # ;
1003    /// ```
1004    pub fn features(&mut self, features: CargoOpt) -> &mut MetadataCommand {
1005        match features {
1006            CargoOpt::SomeFeatures(features) => self.features.extend(features),
1007            CargoOpt::NoDefaultFeatures => {
1008                assert!(
1009                    !self.no_default_features,
1010                    "Do not supply CargoOpt::NoDefaultFeatures more than once!"
1011                );
1012                self.no_default_features = true;
1013            }
1014            CargoOpt::AllFeatures => {
1015                assert!(
1016                    !self.all_features,
1017                    "Do not supply CargoOpt::AllFeatures more than once!"
1018                );
1019                self.all_features = true;
1020            }
1021        }
1022        self
1023    }
1024    /// Arbitrary command line flags to pass to `cargo`.  These will be added
1025    /// to the end of the command line invocation.
1026    pub fn other_options(&mut self, options: impl Into<Vec<String>>) -> &mut MetadataCommand {
1027        self.other_options = options.into();
1028        self
1029    }
1030
1031    /// Arbitrary environment variables to set when running `cargo`.  These will be merged into
1032    /// the calling environment, overriding any which clash.
1033    ///
1034    /// Some examples of when you may want to use this:
1035    /// 1. Setting cargo config values without needing a .cargo/config.toml file, e.g. to set
1036    ///    `CARGO_NET_GIT_FETCH_WITH_CLI=true`
1037    /// 2. To specify a custom path to RUSTC if your rust toolchain components aren't laid out in
1038    ///    the way cargo expects by default.
1039    ///
1040    /// ```no_run
1041    /// # use cargo_metadata::{CargoOpt, MetadataCommand};
1042    /// MetadataCommand::new()
1043    ///     .env("CARGO_NET_GIT_FETCH_WITH_CLI", "true")
1044    ///     .env("RUSTC", "/path/to/rustc")
1045    ///     // ...
1046    ///     # ;
1047    /// ```
1048    pub fn env<K: Into<OsString>, V: Into<OsString>>(
1049        &mut self,
1050        key: K,
1051        val: V,
1052    ) -> &mut MetadataCommand {
1053        self.env.insert(key.into(), Some(val.into()));
1054        self
1055    }
1056
1057    /// Arbitrary environment variables to remove when running `cargo`.  These will be merged into
1058    /// the calling environment, overriding any which clash.
1059    ///
1060    /// Some examples of when you may want to use this:
1061    /// - Removing inherited environment variables in build scripts that can cause an error
1062    ///   when calling `cargo metadata` (for example, when cross-compiling).
1063    ///
1064    /// ```no_run
1065    /// # use cargo_metadata::{CargoOpt, MetadataCommand};
1066    /// MetadataCommand::new()
1067    ///     .env_remove("CARGO_ENCODED_RUSTFLAGS")
1068    ///     // ...
1069    ///     # ;
1070    /// ```
1071    pub fn env_remove<K: Into<OsString>>(&mut self, key: K) -> &mut MetadataCommand {
1072        self.env.insert(key.into(), None);
1073        self
1074    }
1075
1076    /// Set whether to show stderr
1077    pub fn verbose(&mut self, verbose: bool) -> &mut MetadataCommand {
1078        self.verbose = verbose;
1079        self
1080    }
1081
1082    /// Builds a command for `cargo metadata`.  This is the first
1083    /// part of the work of `exec`.
1084    pub fn cargo_command(&self) -> Command {
1085        let cargo = self
1086            .cargo_path
1087            .clone()
1088            .or_else(|| env::var("CARGO").map(PathBuf::from).ok())
1089            .unwrap_or_else(|| PathBuf::from("cargo"));
1090        let mut cmd = Command::new(cargo);
1091        cmd.args(["metadata", "--format-version", "1"]);
1092
1093        if self.no_deps {
1094            cmd.arg("--no-deps");
1095        }
1096
1097        if let Some(path) = self.current_dir.as_ref() {
1098            cmd.current_dir(path);
1099        }
1100
1101        if !self.features.is_empty() {
1102            cmd.arg("--features").arg(self.features.join(","));
1103        }
1104        if self.all_features {
1105            cmd.arg("--all-features");
1106        }
1107        if self.no_default_features {
1108            cmd.arg("--no-default-features");
1109        }
1110
1111        if let Some(manifest_path) = &self.manifest_path {
1112            cmd.arg("--manifest-path").arg(manifest_path.as_os_str());
1113        }
1114        cmd.args(&self.other_options);
1115
1116        for (key, val) in &self.env {
1117            match val {
1118                Some(val) => cmd.env(key, val),
1119                None => cmd.env_remove(key),
1120            };
1121        }
1122
1123        cmd
1124    }
1125
1126    /// Parses `cargo metadata` output.  `data` must have been
1127    /// produced by a command built with `cargo_command`.
1128    pub fn parse<T: AsRef<str>>(data: T) -> Result<Metadata> {
1129        let meta = serde_json::from_str(data.as_ref())?;
1130        Ok(meta)
1131    }
1132
1133    /// Runs configured `cargo metadata` and returns parsed `Metadata`.
1134    pub fn exec(&self) -> Result<Metadata> {
1135        let mut command = self.cargo_command();
1136        if self.verbose {
1137            command.stderr(Stdio::inherit());
1138        }
1139        let output = command.output()?;
1140        if !output.status.success() {
1141            return Err(Error::CargoMetadata {
1142                stderr: String::from_utf8(output.stderr)?,
1143            });
1144        }
1145        let stdout = from_utf8(&output.stdout)?
1146            .lines()
1147            .find(|line| line.starts_with('{'))
1148            .ok_or(Error::NoJson)?;
1149        Self::parse(stdout)
1150    }
1151}
1152
1153/// As per the Cargo Book the [`rust-version` field](https://doc.rust-lang.org/cargo/reference/manifest.html#the-rust-version-field) must:
1154///
1155/// > be a bare version number with two or three components;
1156/// > it cannot include semver operators or pre-release identifiers.
1157///
1158/// [`semver::Version`] however requires three components. This function takes
1159/// care of appending `.0` if the provided version number only has two components
1160/// and ensuring that it does not contain a pre-release version or build metadata.
1161fn deserialize_rust_version<'de, D>(
1162    deserializer: D,
1163) -> std::result::Result<Option<Version>, D::Error>
1164where
1165    D: Deserializer<'de>,
1166{
1167    let mut buf = match Option::<String>::deserialize(deserializer)? {
1168        None => return Ok(None),
1169        Some(buf) => buf,
1170    };
1171
1172    for char in buf.chars() {
1173        if char == '-' {
1174            return Err(serde::de::Error::custom(
1175                "pre-release identifiers are not supported in rust-version",
1176            ));
1177        } else if char == '+' {
1178            return Err(serde::de::Error::custom(
1179                "build metadata is not supported in rust-version",
1180            ));
1181        }
1182    }
1183
1184    if buf.matches('.').count() == 1 {
1185        // e.g. 1.0 -> 1.0.0
1186        buf.push_str(".0");
1187    }
1188
1189    Ok(Some(
1190        Version::parse(&buf).map_err(serde::de::Error::custom)?,
1191    ))
1192}
1193
1194#[cfg(test)]
1195mod test {
1196    use semver::Version;
1197
1198    #[derive(Debug, serde::Deserialize)]
1199    struct BareVersion(
1200        #[serde(deserialize_with = "super::deserialize_rust_version")] Option<semver::Version>,
1201    );
1202
1203    fn bare_version(str: &str) -> Version {
1204        serde_json::from_str::<BareVersion>(&format!(r#""{}""#, str))
1205            .unwrap()
1206            .0
1207            .unwrap()
1208    }
1209
1210    fn bare_version_err(str: &str) -> String {
1211        serde_json::from_str::<BareVersion>(&format!(r#""{}""#, str))
1212            .unwrap_err()
1213            .to_string()
1214    }
1215
1216    #[test]
1217    fn test_deserialize_rust_version() {
1218        assert_eq!(bare_version("1.2"), Version::new(1, 2, 0));
1219        assert_eq!(bare_version("1.2.0"), Version::new(1, 2, 0));
1220        assert_eq!(
1221            bare_version_err("1.2.0-alpha"),
1222            "pre-release identifiers are not supported in rust-version"
1223        );
1224        assert_eq!(
1225            bare_version_err("1.2.0+123"),
1226            "build metadata is not supported in rust-version"
1227        );
1228    }
1229}