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